Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

搭建前端环境

注册微信开发者账号

打开微信公总平台,按照流程一步步注册:https://mp.weixin.qq.com/

然后去申请开通三个我们项目会用到的接口
![[Pasted image 20250417092751.png]]

以及我们需要用到的插件
![[Pasted image 20250417093027.png]]
https://fuwu.weixin.qq.com/search?tab=3&type=&serviceType=3&page=1&kw=腾讯位置服务地图选点
fuwu.weixin.qq.com/search?tab=3&type=&serviceType=3&page=1&kw=微信同声传译

安装 node.js 和微信开发者工具

下载node.js:https://nodejs.org/en/download 版本选择16.20.0
![[Pasted image 20250417093149.png]]

微信开发者工具也是点击下载一步步走就行

  • 下载两遍,安装两个相同的,方便后期项目两个端口一起调试
    ![[Pasted image 20250417091648.png]]
    ![[Pasted image 20250417091741.png]]

在开发者工具中运行前端代码

  • 点击左上角项目栏下的导入项目
    ![[Pasted image 20250417093655.png]]

  • 点击上方设置栏中的安全设置,打开里面的服务端口
    ![[Pasted image 20250417093936.png]]

另一个也是一样

搭建后端环境

安装软件环境

安装rabbitmq

  • **第一步 拉取镜像

`docker pull rabbitmq:3.9.0-management

  • **第二步 使用容器启动服务

`docker run -d –name=rabbitmq –restart=always -p 5672:5672 -p 15672:15672 rabbitmq:3.9.0-management  

  • **第三步 安装延迟队列插件

1、首先下载rabbitmq_delayed_message_exchange-3.9.0.ez文件上传到RabbitMQ所在服务器,下载地址:https://www.rabbitmq.com/community-plugins.html

2、上传下载延迟队列插件到Linux操作系统中,切换到插件所在目录,

执行 docker cp rabbitmq_delayed_message_exchange-3.9.0.ez rabbitmq:/plugins 命令,将刚插件拷贝到容器内plugins目录下

3、执行 docker exec -it rabbitmq /bin/bash 命令进入到容器内部,并 cd plugins 进入plugins目录

执行 ls -l|grep delay  命令查看插件是否copy成功
在容器内plugins目录下,执行 rabbitmq-plugins enable rabbitmq_delayed_message_exchange  命令启用插件

4、exit命令退出RabbitMQ容器内部,然后执行 docker restart rabbitmq 命令重启RabbitMQ容器

  • **第四步 远程访问设置凭证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 进入容器
    docker exec -it rabbitmq bash

    # 创建新用户(用户名:admin,密码:Abc123)
    rabbitmqctl add_user admin Abc123

    # 赋予管理员权限
    rabbitmqctl set_user_tags admin administrator

    # 赋予所有权限(虚拟主机为 /)
    rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

安装redis

  • **第一步 拉取镜像

docker pull redis

  • **第二步 创建Redis配置文件
1
2
3
4
## 创建目录
mkdir -p /home/redis/conf
## 创建文件
touch /home/redis/conf/redis.conf
  • **第三步 创建redis并启动
1
2
3
4
5
6
7
8
docker run -d \
--name redis \
-p 6379:6379 \
--restart unless-stopped \
-v /home/redis/data:/data \
-v /home/redis/conf/redis.conf:/etc/redis/redis.conf \
redis:latest \
redis-server /etc/redis/redis.conf
  • **第四步 进入redis容器
1
2
3
4
5
6
7
8
### 直接通过Docker Redis 命令进入Redis控制台
docker exec -it redis redis-cli
### 进入 Redis 控制台
redis-cli
### 添加一个变量为 key 为 name , value 为 bella 的内容
> set name bella
### 查看 key 为 name 的 value 值
> get name

安装minio

1
2
3
4
5
6
7
8
9
10
 docker run \
--name minio_one \
-p 9000:9000 \
-p 9001:9001 \
-d \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-v /root/minio-data:/data \
-v /root/minio-config:/root/.minio \
minio/minio server /data --console-address ":9001"
  • docker run:这是Docker命令行工具用来运行一个新容器的命令。

  • –name minio_one:这个参数为容器指定了一个名称,这里名称被设置为minio_one。使用名称可以更方便地管理容器。

  • p 9000:9000:这个参数将容器内的9000端口映射到宿主机的9000端口。MinIO服务默认使用9000端口提供API服务。

  • -p 9001:9001:这个参数将容器内的9001端口映射到宿主机的9001端口。这是MinIO的控制台(Console)端口,用于访问MinIO的图形用户界面。

  • -d:这个参数告诉Docker以“detached”模式运行容器,即在后台运行。

  • -e “MINIO_ROOT_USER=admin”:设置环境变量MINIO_ROOT_USER,这是访问MinIO服务的用户名称,这里设置为admin。

  • -e “MINIO_ROOT_PASSWORD=admin123456”:设置环境变量MINIO_ROOT_PASSWORD,这是访问MinIO服务的用户密码,这里设置为admin123456。

  • -v /home/data:/data:这个参数将宿主机的目录/home/data:/data挂载到容器的/data目录。MinIO会将所有数据存储在这个目录。

  • -v /root/config:/root/.minio:这个参数将宿主机的目录/root/minio-config挂载到容器的/root/.minio目录。这个目录用于存储MinIO的配置文件和数据。

  • minio/minio:这是要运行的Docker镜像的名称,这里使用的是官方发布的MinIO镜像。

  • server /data:这是传递给MinIO程序的命令行参数,告诉MinIO以服务器模式运行,并且使用/data目录作为其数据存储位置。

  • –console-address “:9001”:这个参数指定MinIO控制台服务的监听地址和端口。在这个例子中,它设置为监听所有接口上的9001端口。

注意:文件上传时,需要调整-下Linux 服务器的时间与Windows 时间一致!

1
2
3
4
5
6
7
8
9
10
11
12
第一步:安装ntp服务
yum -y install ntp
第二步:开启开机启动服务
systemctl enable ntpd
第三步:启动服务
systemctl start ntpd
第四步:更改时区
timedatectl set-timezone Asia/Shanghai
第五步:启用ntp同步
timedatectl set-ntp yes
第六步:同步时间
ntpq -p

如果启动不了,那么就重构主义,推了重建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 强制删除旧容器(保留数据卷)
docker rm -f minio_one

# 重新运行 MinIO 容器(使用正确的镜像名称 minio/minio)
docker run \
--name minio_one \
-p 9000:9000 \
-p 9001:9001 \
-d \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-v /root/minio-data:/data \
-v /root/minio-config:/root/.minio \
minio/minio server /data --console-address ":9001"

nacos

  • 开启nacos

bash /root/nacos/bin/startup.sh -m standalone

  • 关闭nacos

bash /root/nacos/bin/shutdown.sh

导入数据库内容

  • 只需要在 DataGrip 连接上服务器或者本地的数据库

  • 然后再把所需要的数据库和表创建好,数据导入进去

逻辑删除

  • 物理删除是与之前一样,直接把数据删除,但这样不方便我们恢复数据,所以 mybatis-plus 有一种删除叫逻辑删除

  • 我们可以在数据表加一列属性表示为逻辑删除,值为0表示没有删除,值为1表示删除,方便我们恢复数据

官方文档:https://baomidou.com/guides/logic-delete/

第一种方法:

1
2
3
4
5
6
7
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete #默认deleted
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto

第二种方法:
添加@TableLogic注解

1
2
3
4
5
6
7
8
9
10
@Data
public class User{
。。。
/**
* 1删除0正常
*/
@TableLogic(value = "1",delval = "0")
private Integer isDelete;
。。。
}

添加完后再使用mp已经封装好的语句都会加上 where deleted = 0

分页查询

  • 实现分页查询前需要配置分页插件

  • 分页插件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Configuration
    @MapperScan("scan.your.mapper.package")
    public class MybatisPlusConfig {

    /**
    * 添加分页插件
    */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
    // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
    return interceptor;
    }
    }

导入配置文件

  • 两种导入模式

第一种:像之前一样一条一条创建

第二种:把文件打成压缩包,但是压缩包名称要与组的名字相同(默认是 DEFAULT_GROUP
![[Pasted image 20250418214153.png]]

客户端登录

微信小程序登录流程

![[Pasted image 20250419174655.png]]

微信小程序登录接口

  • 这是接下来需要实现的步骤

项目工程结构:
![[Pasted image 20250419180421.png]]

准备工作

  • **导入微信工具包相关依赖

在 service-customer 中引入依赖

1
2
3
4
<dependency>  
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
</dependency>
  • **修改 nacos 配置中心文件

    • 因为查看官方文档可以看到我们后端请求腾讯接口需要三个值:小程序id、密钥和临时票据code,所以我们需要去 nacos 修改配置文件,把密钥和小程序id先作配置

    • 注意查看项目配置文件 bootstrap.properties 里 nacos 的 IP 与端口是否正确

  • 创建配置类来读取配置文件信息

在 service-customer 创建包config,创建类来读取配置文件中的内容

1
2
3
4
5
6
7
8
@Component
@Data
//从配置文件中读取 wx.miniapp 前缀下的字段
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfigProperties {
private String appId;
private String secret;
}

创建微信工具包对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class WxConfigOperator {

@Autowired
private WxConfigProperties wxConfigProperties;

@Bean
public WxMaService wxMaService() {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(wxConfigProperties.getAppId());
config.setSecret(wxConfigProperties.getSecret());

WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(config);
return service;
}
}

功能实现-基础功能

  • 在 service-customer 的 CustomerInfoController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Slf4j
    @RestController
    @RequestMapping("/customer/info")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CustomerInfoController {

    @Autowired
    private CustomerInfoService customerInfoService;

    // 微信小程序登录接口
    @Operation(summary = "小程序授权登录")
    @GetMapping("login/{code}")
    public Result<Long> longin(@PathVariable String code) {
    return Result.ok(customerInfoService.login(code));
    }
    }
  • service实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    @Slf4j
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, CustomerInfo> implements CustomerInfoService {

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private CustomerInfoMapper customerInfoMapper;

    @Autowired
    private CustomerLoginLogMapper customerLoginLogMapper;

    //微信小程序登录
    @Override
    public Long login(String code) {
    String openid = null;
    // 获取code值,通过微信工具包对象,获取唯一标识openId
    try {
    WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
    openid = sessionInfo.getOpenid();
    } catch (WxErrorException e) {
    throw new RuntimeException(e);
    }

    // 用openId查询数据库是否存在
    // 如果openId不存在返回null,如果存在返回记录
    LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper();
    wrapper.eq(CustomerInfo::getWxOpenId, openid);
    CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);

    // 如果不存在,也就是第一次登陆,添加信息到用户表
    if (customerInfo == null) {
    customerInfo = new CustomerInfo();
    //用当前时间戳作为昵称
    customerInfo.setNickname(String.valueOf(System.currentTimeMillis()));
    //设置默认头像
    customerInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
    //把openId存入用户表
    customerInfo.setWxOpenId(openid);
    customerInfoMapper.insert(customerInfo);
    }

    // 记录登录日志
    CustomerLoginLog loginLog = new CustomerLoginLog();
    loginLog.setCustomerId(customerInfo.getId());
    loginLog.setMsg("小程序登录");
    customerLoginLogMapper.insert(loginLog);

    // 最后返回用户id
    return customerInfo.getId();
    }
    }

功能实现-远程调用

  • service-customer-client 定义接口

    1
    2
    3
    4
    5
    6
    7
    @FeignClient(value = "service-customer")
    public interface CustomerInfoFeignClient {

    @GetMapping("/customer/info/login/{code}")
    public Result<Long> longin(@PathVariable String code);

    }
  • 在 web-customer 进行远程调用
    controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Slf4j
    @Tag(name = "客户API接口管理")
    @RestController
    @RequestMapping("/customer")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CustomerController {

    @Autowired
    private CustomerService customerInfoService;

    @Operation(summary = "小程序授权登录")
    @GetMapping("/login/{code}")
    public Result<String> wxLogin(@PathVariable String code) {
    return Result.ok(customerInfoService.login(code));
    }
    }

service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class CustomerServiceImpl implements CustomerService {

// 注入远程调用
@Autowired
private CustomerInfoFeignClient client;

@Autowired
private RedisTemplate redisTemplate;

@Override
public String login(String code) {

// 拿着code进行远程调用,返回用户id
Result<Long> longin = client.longin(code);

// 如果返回失败了。返回错误信息
Integer code1 = longin.getCode();
if(code1 != 200){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}

// 获取远程调用的用户id
Long customerId = longin.getData();

// 判断返回用户id是否为空,为空则返回错误提示
if(customerId == null){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}

// 把用户id放到Redis中,设置过期时间
String token = UUID.randomUUID().toString();

// 返回token
redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX+token,
customerId.toString(),
RedisConstant.USER_LOGIN_KEY_TIMEOUT,
TimeUnit.SECONDS);
return token;
}
}

功能实现-测试

  • 运行乘客端微信小程序(微信开发者工具)

  • 启动后端服务

    • 启动网关

    • 启动 service-customer 服务

    • 启动 web-customer 服务

把用到的配置文件都检查一遍,重点看看IP地址和开放端口,以及MySQL的远程用户(就是因为这个问题,后面用映射成localhost解决了)

获取客户端登录信息

service-customer 接口

  • Controller

    1
    2
    3
    4
    5
    6
    @Operation(summary = "获取客户端登录信息")
    @GetMapping("/getCustomerLoginInfo/{customerId}")
    public Result<CustomerLoginVo> getCustomerLoginInfo(@PathVariable Long customerId) {
    CustomerLoginVo customerLoginVo = customerInfoService.getCustomerInfoService(customerId);
    return Result.ok(customerLoginVo);
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Override
    public CustomerLoginVo getCustomerInfoService(Long customerId) {

    // 根据用户id查询用户信息
    // LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper();
    // wrapper.eq(CustomerInfo::getId, customerId);
    // CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);
    CustomerInfo info = customerInfoMapper.selectById(customerId);
    if (info == null) {
    throw new RuntimeException("用户不存在");
    }

    // 封装到customerLoginVo对象
    CustomerLoginVo customerLoginVo = new CustomerLoginVo();

    // springboot的工具类,可以把第一个对象的属性复制到第二个对象的同名属性中
    BeanUtils.copyProperties(info, customerLoginVo);

    /*
    * 还有一个属性需要手动拷贝,判断是否存在手机号
    * StringUtils.hasText(info.getPhone())判断字符串是否有值
    */
    customerLoginVo.setIsBindPhone(StringUtils.hasText(info.getPhone()));

    // 返回对象
    return customerLoginVo;
    }

web-customer 接口

  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    @Operation(summary = "获取客户登录信息")
    @GetMapping("/getCustomerLoginInfo")
    public Result<CustomerLoginVo> getCustomerLoginInfo(@RequestHeader(value = "token") String token) {

    CustomerLoginVo customerLoginVo = customerInfoService.getCustomerLoginInfo(token);
    // 返回用户对象
    return Result.ok(customerLoginVo);
    }
  • service

    • 这里的token要和前面设置的一样
    • redisTemplate.opsForValue().get() 就是把redis当成一个map容器,通过键值对的方式来存储或查询
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      @Override
      public CustomerLoginVo getCustomerLoginInfo(String token) {
      /*
      * 从请求头中获取token字符串
      * 根据token查询redis
      */
      String customerId = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX+token);

      // 根据用户id进行远程调用,获取用户信息
      if(!StringUtils.hasText(customerId)){
      throw new GuiguException(ResultCodeEnum.DATA_ERROR);
      }

      Result<CustomerLoginVo> customerLoginInfo = client.getCustomerLoginInfo(Long.parseLong(customerId));
      Integer code = customerLoginInfo.getCode();
      if(code != 200){
      throw new GuiguException(ResultCodeEnum.DATA_ERROR);
      }
      CustomerLoginVo data = customerLoginInfo.getData();
      if(data == null){
      throw new GuiguException(ResultCodeEnum.DATA_ERROR);
      }
      return data;
      }

登录校验

这里选用 AOP面向切面加自定义注解完成,有想过用拦截器来做,但是路径太多,懒得配置了(高情商,更希望学习底层知识)

创建自定义注解

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginDetection {

}

创建切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Component
@Aspect
public class LoginAspect {

@Autowired
private RedisTemplate redisTemplate;

// 环绕通知,运行前后都触发
// 切入点表达式,用来指定那些规则的方法进行增强
@Around("execution(* com.atguigu.daijia.*.controller.*.*(..)) && @annotation(loginDetection)")
public Object login(ProceedingJoinPoint proceedingJoinPoint,
LoginDetection loginDetection) throws Throwable {

// 获取request对象,并且从请求头获取token
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;

String token = sra.getRequest().getHeader("token");


// 判断token是否有效,如果为空或过期,返回登录提示
if(!StringUtils.hasText(token)){
throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
}

// token不为空,查询redis、对应用户id,把用户id放到ThreadLocal里面
String customerId = (String) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);

if(StringUtils.hasText(customerId)){
AuthContextHolder.setUserId(Long.parseLong(customerId));
}

// 执行目标方法
return proceedingJoinPoint.proceed();
}
}

把之前的getCustomerLoginInfo用上注解

在web-customer中的service里 getCustomerLoginInfo 做修改,用上我们刚刚写的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Operation(summary = "获取客户登录信息")
@LoginDetection
@GetMapping("/getCustomerLoginInfo")
public Result<CustomerLoginVo> getCustomerLoginInfo() {

// 从ThreadLocal获取用户id
Long userId = AuthContextHolder.getUserId();

// 调用service
CustomerLoginVo customerLoginVo = customerInfoService.getCustomerInfo(userId);

// 返回用户对象
return Result.ok(customerLoginVo);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public CustomerLoginVo getCustomerInfo(Long customerId) {

Result<CustomerLoginVo> customerLoginInfo = client.getCustomerLoginInfo(customerId);
Integer code = customerLoginInfo.getCode();
if(code != 200){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
CustomerLoginVo data = customerLoginInfo.getData();
if(data == null){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
return data;
}

获取乘客手机号

可恶,写完的时候发现个人版的微信开发无法使用这个,白写,最后不做判断直接改成true了

controller

1
2
3
4
5
@Operation(summary = "更新客户微信手机号码")
@PostMapping("/updateWxPhoneNumber")
public Result<Boolean> updateWxPhoneNumber(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
return Result.ok(customerInfoService.updateWxPhoneNumber(updateWxPhoneForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 更新客户微信手机号码
@Override
public Boolean updateWxPhoneNumber(UpdateWxPhoneForm updateWxPhoneForm) {

// 根据code查询用户信息
try {
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(updateWxPhoneForm.getCode());

// 更新用户信息
Long customerId = updateWxPhoneForm.getCustomerId();
CustomerInfo customerInfo = customerInfoMapper.selectById(customerId);
customerInfo.setPhone(phoneNoInfo.getPhoneNumber());
customerInfoMapper.updateById(customerInfo);

return true;
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
}

web-customer

  • controller
    企业版
    1
    2
    3
    4
    5
    6
    7
    @Operation(summary = "更新用户微信手机号")
    @LoginDetection
    @PostMapping("/updateWxPhone")
    public Result updateWxPhone(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
    updateWxPhoneForm.setCustomerId(AuthContextHolder.getUserId());
    return Result.ok(customerInfoService.updateWxPhoneNumber(updateWxPhoneForm));
    }

个人版:

1
2
3
4
5
6
7
@Operation(summary = "更新用户微信手机号")
@LoginDetection
@PostMapping("/updateWxPhone")
public Result updateWxPhone(@RequestBody UpdateWxPhoneForm updateWxPhoneForm) {
updateWxPhoneForm.setCustomerId(AuthContextHolder.getUserId());
return Result.ok(true);
}
  • service
    1
    2
    3
    4
    5
    @Override
    public boolean updateWxPhoneNumber(UpdateWxPhoneForm updateWxPhoneForm) {
    Result<Boolean> booleanResult = client.updateWxPhoneNumber(updateWxPhoneForm);
    return true;
    }

司机端登录

司机开启接单的条件:

1、登录

2、认证通过

3、建立了腾讯云人员库人员

4、当日验证了人脸识别

准备工作与流程

  • 准备共奏

因为司机端要完成认证模块,比如身份证,驾驶证之类的,所以我们需要用到腾讯云的对象存储COS
还有腾讯云的OCR来识别身份证和驾驶证

  • 流程如下
    • 司机端登录功能
    • 获取用户信息
    • 腾讯云对象存储COS
    • 上传信息接口
    • 腾讯云OCR,识别身份证与驾驶证
    • 人脸识别
  • 修改项目配置文件和Nacos里面配置文件内容

  • 创建类,读取配置文件内容,微信小程序id和秘钥,这个跟客户端的一样,直接复制过来

    1
    2
    3
    4
    5
    6
    7
    @Component
    @Data
    @ConfigurationProperties(prefix = "wx.miniapp")
    public class WxConfigProperties {
    private String appId;
    private String secret;
    }
  • 创建类,初始化微信工具包相关对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Component
    public class WxConfigOperator {

        @Autowired
        private WxConfigProperties wxConfigProperties;

        @Bean
        public WxMaService wxMaService() {
            //微信小程序id和秘钥
            WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl();
            wxMaConfig.setAppid(wxConfigProperties.getAppId());
            wxMaConfig.setSecret(wxConfigProperties.getSecret());
            WxMaService service = new WxMaServiceImpl();
            service.setWxMaConfig(wxMaConfig);
            return service;
        }
    }

司机端登录功能

这个步骤跟之前客户端差不多

  • service:
    • 就是获取code,然后通过code加密钥和小程序id去获取用户的唯一表示openid
    • 判断这个用户是不是第一次登录
    • 是的话初始化用户
    • 返回id
  • web-service:
    • 获取code,调用service
    • 远程调用获取数据
    • 生成token
    • 放入redis
    • 返回token

service-drive

DriverInfoController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Tag(name = "司机API接口管理")
@RestController
@RequestMapping(value="/driver/info")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverInfoController {

@Autowired
private DriverInfoService driverInfoService;

@Operation(summary = "小程序授权登录")
@GetMapping("/login/{code}")
public Result<Long> login(@PathVariable("code") String code) {
return Result.ok(driverInfoService.login(code));
}
}

DriverInfoServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverInfoServiceImpl extends ServiceImpl<DriverInfoMapper, DriverInfo> implements DriverInfoService {

@Autowired
private WxMaService wxMaService;

@Autowired
private DriverInfoMapper driverInfoMapper;

@Autowired
private DriverSetMapper driverSetMapper;

@Autowired
private DriverAccountMapper driverAccountMapper;

@Autowired
private DriverLoginLogMapper driverLoginLogMapper;

// 小程序授权登录
@Override
public Long login(String code) {
try {
// 通过code+密钥+小程序id获取用户唯一标识openid
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
String openid = sessionInfo.getOpenid();

// 根据openid查询用户信息
LambdaQueryWrapper<DriverInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DriverInfo::getWxOpenId, openid);
DriverInfo driverInfo = driverInfoMapper.selectOne(wrapper);

if(driverInfo == null){
// 添加司机基本信息
driverInfo = new DriverInfo();
driverInfo.setNickname(String.valueOf(System.currentTimeMillis()));
driverInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
driverInfo.setWxOpenId(openid);
driverInfoMapper.insert(driverInfo);

// 初始化司机设置
DriverSet driverSet = new DriverSet();
driverSet.setDriverId(driverInfo.getId());
driverSet.setOrderDistance(new BigDecimal(0));//0:无限制
driverSet.setAcceptDistance(new BigDecimal(SystemConstant.ACCEPT_DISTANCE));//默认接单范围:5公里
driverSet.setIsAutoAccept(0);//0:否 1:是
driverSetMapper.insert(driverSet);

// 初始化司机账户信息
DriverAccount driverAccount = new DriverAccount();
driverAccount.setDriverId(driverInfo.getId());
driverAccountMapper.insert(driverAccount);
}

// 更新登录日志
DriverLoginLog loginLog = new DriverLoginLog();
loginLog.setDriverId(driverInfo.getId());
loginLog.setMsg("小程序登录");
driverLoginLogMapper.insert(loginLog);

// 返回用户id
return driverInfo.getId();
} catch (WxErrorException e) {
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}
}

service-drive-client

1
2
3
4
5
6
7
@FeignClient(value = "service-driver")
public interface DriverInfoFeignClient {

// 小程序授权登录
@GetMapping("/driver/info/login/{code}")
public Result<Long> login(@PathVariable("code") String code);
}

web-drive

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Tag(name = "司机API接口管理")
@RestController
@RequestMapping(value="/driver")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverController {

@Autowired
private DriverService driverService;

@Operation(summary = "小程序授权登录")
@GetMapping("/login/{code}")
public Result<String> login(@PathVariable("code") String code) {
return Result.ok(driverService.login(code));
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverServiceImpl implements DriverService {

@Autowired
private DriverInfoFeignClient driverInfoFeignClient;

@Autowired
private RedisTemplate redisTemplate;

// 小程序授权登录
@Override
public String login(String code) {
Result<Long> result = driverInfoFeignClient.login(code);
if(result.getCode() != 200){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
Long data = result.getData();
if(data == null){
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
String token = UUID.randomUUID().toString().replace("-","");
redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + token,
data.toString(),
RedisConstant.USER_LOGIN_KEY_TIMEOUT,
TimeUnit.SECONDS);

return token;
}
}

获取登录司机信息

service-driver

controller

1
2
3
4
5
6
@Operation(summary = "获取司机登录信息")  
@GetMapping("/getDriverLoginInfo/{driverId}")
public Result<DriverLoginVo> getDriverInfo(@PathVariable("driverId") long driverId) {
DriverLoginVo driverLoginVo = driverInfoService.getDriverInfo(driverId);
return Result.ok(driverLoginVo);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取司机登录信息
@Override
public DriverLoginVo getDriverInfo(long driverId) {
// 通过id查询司机信息
DriverInfo driverInfo = driverInfoMapper.selectById(driverId);

// 赋予到vo对象中
DriverLoginVo driverLoginVo = new DriverLoginVo();
BeanUtils.copyProperties(driverInfo, driverLoginVo);

// 查询是否需要建档人脸识别
String faceModelId = driverInfo.getFaceModelId();
boolean isArchiveFace = StringUtils.hasText(faceModelId);
driverLoginVo.setIsArchiveFace(isArchiveFace);
return driverLoginVo;
}

service-driver-client

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(value = "service-driver")
public interface DriverInfoFeignClient {

// 小程序授权登录
@GetMapping("/driver/info/login/{code}")
public Result<Long> login(@PathVariable("code") String code);

// 获取登录司机信息
@GetMapping("/driver/info/getDriverLoginInfo/{driverId}")
public Result<DriverLoginVo> getDriverInfo(@PathVariable("driverId") long driverId);
}

web-driver

controller

1
2
3
4
5
6
@Operation(summary = "获取登录司机信息")
@LoginDetection
@GetMapping("/getDriverLoginInfo")
public Result<DriverLoginVo> getDriverLoginInfo() {
return Result.ok(driverService.getDriverLoginVo());
}

service

1
2
3
4
5
6
@Override
public DriverLoginVo getDriverLoginVo() {
Long driverId = AuthContextHolder.getUserId();
Result<DriverLoginVo> driverInfo = driverInfoFeignClient.getDriverInfo(driverId);
return driverInfo.getData();
}

测试一下前面的代码

  • 打开微信开发者工具
  • 改好相关配置文件
  • nacos
  • 启动后端服务
    • 网关服务service-gateway
    • 司机端基础服务service-driver
    • 对外访问服务web-driver

可恶,在这里前面web-driver里面controller的路径写错了,一直访问不上,还有nacos里面的配置忘记修改成映射后的端口

开通腾讯云对象存储COS

准备工作

  • 注册并实名腾讯云
  • 找到对象存储
  • 创建储存桶
  • 创建并保存密钥和id

思路

在 web-driver:

  • 获取文件以及存放路径
  • 远程调用实现文件上传
  • 返回 vo 对象

在 service-driver:

  • 导入依赖
  • 获取上传文件及路径
  • 修改配置文件
  • 基于腾讯云官方文档调用实现上传
  • 返回 vo 对象

注意点:因为保密隐私,我们这里的存储桶肯定是私密的,但是我们又要让用户能看到上传后的结果,因此我们需要创建一个临时地址,让客户看到存储内容

web-service

  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Tag(name = "上传管理接口")
    @RestController
    @RequestMapping("file")
    public class FileController {

    @Autowired
    private CosService cosService;

    // 文件上传接口
    @Operation(summary = "上传")
    @LoginDetection
    @PostMapping("/upload")
    public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
    @RequestParam(value = "path", defaultValue = "auth") String path) {
    CosUploadVo cosUploadVo = cosService.uploadFile(file, path);
    return Result.ok(cosUploadVo);
    }
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Slf4j
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CosServiceImpl implements CosService {

    @Autowired
    private CosFeignClient cosFeignClient;

    // 文件上传接口
    @Override
    public CosUploadVo uploadFile(MultipartFile file, String path) {
    // 远程调用获取vo
    Result<CosUploadVo> result = cosFeignClient.upload(file, path);
    CosUploadVo cosUploadVo = result.getData();
    return cosUploadVo;
    }
    }

service-driver-client

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(value = "service-driver")
public interface CosFeignClient {

/*
* 文件上传
* MediaType 表示上传类型
*/
@PostMapping(value = "/cos/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
@RequestParam(value = "path") String path);
}

service-driver

  • 导入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.227</version>
    </dependency>
  • 修改配置文件

  • 创建类读取配置文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Data
    @Component
    @ConfigurationProperties(prefix = "tencent.cloud")
    public class TencentCloudProperties {
    private String secretId;
    private String secretKey;
    private String region;
    private String bucketPrivate;
    }
  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Slf4j
    @Tag(name = "腾讯云cos上传接口管理")
    @RestController
    @RequestMapping(value="/cos")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CosController {

    @Autowired
    private CosService cosService;

    @Operation(summary = "上传")
    @PostMapping("/upload")
    public Result<CosUploadVo> upload(@RequestPart("file") MultipartFile file,
    @RequestParam("path") String path) {
    CosUploadVo cosUploadVo = cosService.upload(file, path);
    return Result.ok(cosUploadVo);
    }
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    @Slf4j
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class CosServiceImpl implements CosService {

    @Autowired
    private TencentCloudProperties tencentCloudProperties;

    // 文件上传
    @Override
    public CosUploadVo upload(MultipartFile file, String path) {

    // 获取cos客户端
    COSClient cosClient = getCosClient();

    // 文件上传
    // 设置文件元数据信息
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentLength(file.getSize());
    metadata.setContentEncoding("UTF-8");
    metadata.setContentType(file.getContentType());

    // 向储存桶保存文件
    String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); //文件后缀名
    String uploadPath = "/driver/" + path + "/" + UUID.randomUUID().toString().replaceAll("-", "") + fileType;
    // 01.jpg
    // /driver/auth/0o98754.jpg
    PutObjectRequest putObjectRequest = null;
    try {
    //1 bucket名称
    //2
    putObjectRequest = new PutObjectRequest(tencentCloudProperties.getBucketPrivate(),
    uploadPath,
    file.getInputStream(),
    metadata);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    putObjectRequest.setStorageClass(StorageClass.Standard);
    PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); //上传文件
    cosClient.shutdown();

    //返回vo对象
    CosUploadVo cosUploadVo = new CosUploadVo();
    cosUploadVo.setUrl(uploadPath);
    // 图片临时访问回显url
    cosUploadVo.setShowUrl(this.getImagerUrl(uploadPath));
    return cosUploadVo;
    }

提取CosClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private COSClient getCosClient() {
// 1 初始化用户身份信息(secretId, secretKey)。
String secretId = tencentCloudProperties.getSecretId();
String secretKey = tencentCloudProperties.getSecretKey();
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置 bucket 的地域
Region region = new Region(tencentCloudProperties.getRegion());
ClientConfig clientConfig = new ClientConfig(region);
// 这里建议设置使用 https 协议
clientConfig.setHttpProtocol(HttpProtocol.https);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);
return cosClient;
}

获取临时签名url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public String getImagerUrl(String path) {
if(!StringUtils.hasText(path)){
return "";
}
COSClient cosClient = getCosClient();
GeneratePresignedUrlRequest request =
new GeneratePresignedUrlRequest(tencentCloudProperties.getBucketPrivate(),
path,
HttpMethodName.GET);
// 设置临时URL有效期
DateTime dateTime = new DateTime().plusMinutes(15);
request.setExpiration(dateTime.toDate());
// 调用方法获取
URL url = cosClient.generatePresignedUrl(request);
cosClient.shutdown();
return url.toString();
}

这一大段看着很复杂,其实大部分都是腾讯云官方文档里面的内容,我们只需要修改一下参数即可

腾讯云身份证与驾驶证识别接口

思路

司机注册成功后,应该需要去实名认证,这里可以用到腾讯云的身份识别和云存储功能,身份证和驾驶证上传的步骤都差不多,这里就写在一起了

那我们现在计划很明确了

web-driver:

  • 获取上传文件
  • 远程调用获取认证信息
  • 返回认证信息

service-driver:

  • 上传认证图片
  • 调用腾讯云相关接口完成认证
  • 返回认证信息

service-driver

  • controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Slf4j
    @Tag(name = "腾讯云识别接口管理")
    @RestController
    @RequestMapping(value="/ocr")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class OcrController {

    @Autowired
    private OcrService ocrService;

    @Operation(summary = "身份证识别")
    @PostMapping("/idCardOcr")
    public Result<IdCardOcrVo> idCardOcr(@RequestPart("file") MultipartFile file) {
    IdCardOcrVo idCardOcrVo = ocrService.idCardOcr(file);
    return Result.ok(idCardOcrVo);
    }

    @Operation(summary = "驾驶证识别")
    @PostMapping("/driverLicenseOcr")
    public Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file") MultipartFile file) {
    DriverLicenseOcrVo driverLicenseOcrVo = ocrService.driverLicenseOcr(file);
    return Result.ok(driverLicenseOcrVo);
    }
    }
  • service:
    导入依赖:

    1
    2
    3
    4
    5
    <dependency>  
    <groupId>com.tencentcloudapi</groupId>
    <artifactId>tencentcloud-sdk-java</artifactId>
    <version>${tencentcloud.version}</version>
    </dependency>

看着代码很多,其实全都是从腾讯云官网拉过来修改配置的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OcrServiceImpl implements OcrService {

@Autowired
private TencentCloudProperties tencentCloudProperties;

@Autowired
private CosService cosService;

// 身份证识别
@Override
public IdCardOcrVo idCardOcr(MultipartFile file) {
try{
// file转换成base64
byte[] base64 = Base64.encodeBase64(file.getBytes());
String fileBase64 = new String(base64);
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
OcrClient client = new OcrClient(cred,tencentCloudProperties.getRegion(), clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
IDCardOCRRequest req = new IDCardOCRRequest();
//设置文件
req.setImageBase64(fileBase64);

// 返回的resp是一个IDCardOCRResponse的实例,与请求对象对应
IDCardOCRResponse resp = client.IDCardOCR(req);

//转换为IdCardOcrVo对象
IdCardOcrVo idCardOcrVo = new IdCardOcrVo();
if (StringUtils.hasText(resp.getName())) {
//身份证正面
idCardOcrVo.setName(resp.getName());
idCardOcrVo.setGender("男".equals(resp.getSex()) ? "1" : "2");
idCardOcrVo.setBirthday(DateTimeFormat.forPattern("yyyy/MM/dd").parseDateTime(resp.getBirth()).toDate());
idCardOcrVo.setIdcardNo(resp.getIdNum());
idCardOcrVo.setIdcardAddress(resp.getAddress());

//上传身份证正面图片到腾讯云cos
CosUploadVo cosUploadVo = cosService.upload(file, "idCard");
idCardOcrVo.setIdcardFrontUrl(cosUploadVo.getUrl());
idCardOcrVo.setIdcardFrontShowUrl(cosUploadVo.getShowUrl());
} else {
//身份证反面
//证件有效期:"2010.07.21-2020.07.21"
String idcardExpireString = resp.getValidDate().split("-")[1];
idCardOcrVo.setIdcardExpire(DateTimeFormat.forPattern("yyyy.MM.dd").parseDateTime(idcardExpireString).toDate());
//上传身份证反面图片到腾讯云cos
CosUploadVo cosUploadVo = cosService.upload(file, "idCard");
idCardOcrVo.setIdcardBackUrl(cosUploadVo.getUrl());
idCardOcrVo.setIdcardBackShowUrl(cosUploadVo.getShowUrl());
}
return idCardOcrVo;
} catch (Exception e) {
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}

//驾驶证识别
@Override
public DriverLicenseOcrVo driverLicenseOcr(MultipartFile file) {
try{
//图片转换base64格式字符串
byte[] base64 = Base64.encodeBase64(file.getBytes());
String fileBase64 = new String(base64);

// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
OcrClient client = new OcrClient(cred, tencentCloudProperties.getRegion(),
clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
DriverLicenseOCRRequest req = new DriverLicenseOCRRequest();
req.setImageBase64(fileBase64);

// 返回的resp是一个DriverLicenseOCRResponse的实例,与请求对象对应
DriverLicenseOCRResponse resp = client.DriverLicenseOCR(req);

//封装到vo对象里面
DriverLicenseOcrVo driverLicenseOcrVo = new DriverLicenseOcrVo();
if (StringUtils.hasText(resp.getName())) {
//驾驶证正面
//驾驶证名称要与身份证名称一致
driverLicenseOcrVo.setName(resp.getName());
driverLicenseOcrVo.setDriverLicenseClazz(resp.getClass_());
driverLicenseOcrVo.setDriverLicenseNo(resp.getCardCode());
driverLicenseOcrVo.setDriverLicenseIssueDate(DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(resp.getDateOfFirstIssue()).toDate());
driverLicenseOcrVo.setDriverLicenseExpire(DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(resp.getEndDate()).toDate());

//上传驾驶证反面图片到腾讯云cos
CosUploadVo cosUploadVo = cosService.upload(file, "driverLicense");
driverLicenseOcrVo.setDriverLicenseFrontUrl(cosUploadVo.getUrl());
driverLicenseOcrVo.setDriverLicenseFrontShowUrl(cosUploadVo.getShowUrl());
} else {
//驾驶证反面
//上传驾驶证反面图片到腾讯云cos
CosUploadVo cosUploadVo = cosService.upload(file, "driverLicense");
driverLicenseOcrVo.setDriverLicenseBackUrl(cosUploadVo.getUrl());
driverLicenseOcrVo.setDriverLicenseBackShowUrl(cosUploadVo.getShowUrl());
}

return driverLicenseOcrVo;
} catch (Exception e) {
e.printStackTrace();
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}
}

service-client

1
2
3
4
5
6
7
8
9
@FeignClient(value = "service-driver")  
public interface OcrFeignClient {

@PostMapping(value = "/ocr/idCardOcr", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<IdCardOcrVo> idCardOcr(@RequestPart("file") MultipartFile file);

@PostMapping(value = "/ocr/driverLicenseOcr", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file") MultipartFile file);
}

web-service

  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Slf4j  
    @Tag(name = "腾讯云识别接口管理")
    @RestController
    @RequestMapping(value="/ocr")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class OcrController {

    @Autowired
    private OcrService ocrService;

    @Operation(summary = "身份证识别")
    @LoginDetection
    @PostMapping("/idCardOcr")
    public Result<IdCardOcrVo> uploadDriverLicenseOcr(@RequestPart("file")MultipartFile file){
    return Result.ok(ocrService.idCardOcr(file));
    }

    @Operation(summary = "驾驶证识别")
    @LoginDetection
    @PostMapping("/driverLicenseOcr")
    public Result<DriverLicenseOcrVo> driverLicenseOcr(@RequestPart("file")MultipartFile file){
    return Result.ok(ocrService.driverLicenseOcr(file));
    }
    }
  • serivce

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Slf4j  
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class OcrServiceImpl implements OcrService {

    @Autowired
    private OcrFeignClient ocrFeignClient;

    // 身份证
    @Override
    public IdCardOcrVo idCardOcr(MultipartFile file) {
    Result<IdCardOcrVo> ocrVoResult = ocrFeignClient.idCardOcr(file);
    return ocrVoResult.getData();
    }

    // 驾驶证
    @Override
    public DriverLicenseOcrVo driverLicenseOcr(MultipartFile file) {
    Result<DriverLicenseOcrVo> result = ocrFeignClient.driverLicenseOcr(file);
    return result.getData();
    }
    }

获取司机认证信息

思路

司机登录后会进入认证界面,判单是否认证,进入认证界面是会显示回显的图片

其实这个很简单,因为腾讯云的那个文字识别会返回参数出来,我们只需要把回显地址返回回去就行

driver-service

  • 通过id来传入回显地址

driver-web

  • 调用该

service-driver

  • controller

    1
    2
    3
    4
    5
    6
    @Operation(summary = "获取司机认证信息")
    @GetMapping("/getDriverAuthInfo/{driverId}")
    public Result<DriverAuthInfoVo> getDriverAuthInfo(@PathVariable Long driverId) {
    DriverAuthInfoVo driverAuthInfoVo = driverInfoService.getDriverAuthInfo(driverId);
    return Result.ok(driverAuthInfoVo);
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ```java
    //获取司机认证信息
    @Override
    public DriverAuthInfoVo getDriverAuthInfo(Long driverId) {
    DriverInfo driverInfo = driverInfoMapper.selectById(driverId);
    DriverAuthInfoVo driverAuthInfoVo = new DriverAuthInfoVo();
    BeanUtils.copyProperties(driverInfo,driverAuthInfoVo);
    driverAuthInfoVo.setIdcardBackShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardBackUrl()));
        driverAuthInfoVo.setIdcardFrontShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardFrontUrl()));

        driverAuthInfoVo.setIdcardHandShowUrl(cosService.getImageUrl(driverAuthInfoVo.getIdcardHandUrl()));

        driverAuthInfoVo.setDriverLicenseFrontShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseFrontUrl()));

        driverAuthInfoVo.setDriverLicenseBackShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseBackUrl()));

        driverAuthInfoVo.setDriverLicenseHandShowUrl(cosService.getImageUrl(driverAuthInfoVo.getDriverLicenseHandUrl()));
        return driverAuthInfoVo;

    }

service-client

1
2
@GetMapping("/driver/info/getDriverAuthInfo/{driverId}")
Result<DriverAuthInfoVo> getDriverAuthInfo(@PathVariable("driverId") Long driverId);

web-serive

  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    @Operation(summary = "获取司机认证信息")
    @GuiguLogin
    @GetMapping("/getDriverAuthInfo")
    public Result<DriverAuthInfoVo> getDriverAuthInfo() {
    //获取登录用户id,当前是司机id
    Long driverId = AuthContextHolder.getUserId();
    return Result.ok(driverService.getDriverAuthInfo(driverId));
    }
  • serivce

    1
    2
    3
    4
    5
    6
    7
    //司机认证信息
    @Override
    public DriverAuthInfoVo getDriverAuthInfo(Long driverId) {
    Result<DriverAuthInfoVo> authInfoVoResult = driverInfoFeignClient.getDriverAuthInfo(driverId);
    DriverAuthInfoVo driverAuthInfoVo = authInfoVoResult.getData();
    return driverAuthInfoVo;
    }

修改司机认证信息

思路

前端点击提交后,后端需要更新客户认证信息
​    0: 未认证 【刚注册完为未认证状态】
​   1:审核中 【提交了认证信息后变为审核中】
​   2:认证通过 【后台审核通过】
​   -1:认证未通过【后台审核不通过】

这个和上面差不多,其他就不写了,就写一个service

1
2
3
4
5
6
7
8
9
// 更新司机认证信息
@Override
public boolean updateDriverAuthInfo(UpdateDriverAuthInfoForm update) {
Long driverId = update.getDriverId();
DriverInfo driverInfo = new DriverInfo();
driverInfo.setId(driverId);
BeanUtils.copyProperties(update, driverInfo);
return this.updateById(driverInfo);
}

开通人脸识别

腾讯云文档:人脸识别_人脸搜索_人脸检测_人脸比对-腾讯云

1、开通腾讯云人脸识别
2、创建人员库

创建司机人脸模型

创建人脸识别模型之后,腾讯云返回模型id,获取模型id,更新到数据库表,但是因为这是调用腾讯云接口,所以如果之前已经有人拿这张图片创建过则不会创建新的模型id,而是返回之前创建的id

  • 修改配置文件

  • 修改配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Data
    @Component
    @ConfigurationProperties(prefix = "tencent.cloud")
    public class TencentCloudProperties {

    private String secretId;
    private String secretKey;
    private String region;
    private String bucketPrivate;

    private String persionGroupId;
    }
  • 根据腾讯云文档来实现代码 API Explorer - 云 API - 控制台

service-driver

后面都是只写重要的代码实现,一般的调用就不写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 创建司机人脸模型
@Override
public Boolean creatDriverFaceModel(DriverFaceModelForm driverFaceModelForm) {
//根据司机id获取司机信息
DriverInfo driverInfo =
driverInfoMapper.selectById(driverFaceModelForm.getDriverId());
try{
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("iai.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
IaiClient client = new IaiClient(cred, tencentCloudProperties.getRegion(),
clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
CreatePersonRequest req = new CreatePersonRequest();
//设置相关值
req.setGroupId(tencentCloudProperties.getPersionGroupId());
//基本信息
req.setPersonId(String.valueOf(driverInfo.getId()));
req.setGender(Long.parseLong(driverInfo.getGender()));
req.setQualityControl(4L);
req.setUniquePersonControl(4L);
req.setPersonName(driverInfo.getName());
req.setImage(driverFaceModelForm.getImageBase64());

// 返回的resp是一个CreatePersonResponse的实例,与请求对象对应
CreatePersonResponse resp = client.CreatePerson(req);
// 输出json格式的字符串回包
System.out.println(AbstractModel.toJsonString(resp));
String faceId = resp.getFaceId();
if(StringUtils.hasText(faceId)) {
driverInfo.setFaceModelId(faceId);
driverInfoMapper.updateById(driverInfo);
}
} catch (TencentCloudSDKException e) {
e.printStackTrace();
return false;
}
return true;
}

预估订单数据

查找客户端当前订单

当一个客户发起订单前先要查询是否有发起并且没有完成的订单,如果有订单未完成,则会弹出进行中的订单

预估驾驶路线

  • 访问腾讯官网 https://lbs.qq.com/

  • 进行注册,手机号或者微信或者其他方式

  • 使用注册账号进行登录,找到控制台

  • 在应用管理 – 我的应用 ,创建应用

  • 在创建应用中,添加key

  • 修改nacos配置文件

  • 编写接口

下面实现接口,具体内容就是请求腾讯云提供的接口,按照接口传参数,然后返回需要的结果

腾讯官方文档:WebService API | 腾讯位置服务

  • 编写配置类

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    public class MyConfig {

    @Bean
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    @Slf4j  
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class MapServiceImpl implements MapService {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${tencent.map.key}")
    private String key;

    //计算驾驶线路
    @Override
    public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {
    //请求腾讯提供接口,按照接口要求传递相关参数,返回需要结果
    //使用HttpClient,目前Spring封装调用工具使用RestTemplate
    //定义调用腾讯地址
    String url = "https://apis.map.qq.com/ws/direction/v1/driving/?from={from}&to={to}&key={key}";

    //封装传递参数
    Map<String,String> map = new HashMap();
    //开始位置
    // 经纬度:比如 北纬40 东京116
    map.put("from",calculateDrivingLineForm.getStartPointLatitude()+","+calculateDrivingLineForm.getStartPointLongitude());
    //结束位置
    map.put("to",calculateDrivingLineForm.getEndPointLatitude()+","+calculateDrivingLineForm.getEndPointLongitude());
    //key
    map.put("key",key);

    //使用RestTemplate调用 GET JSONObject result = restTemplate.getForObject(url, JSONObject.class, map);
    //处理返回结果
    //判断调用是否成功
    int status = Objects.requireNonNull(result).getIntValue("status");
    if(status != 0) {//失败
    throw new GuiguException(ResultCodeEnum.MAP_FAIL);
    }

    //获取返回路线信息
    JSONObject route =
    result.getJSONObject("result").getJSONArray("routes").getJSONObject(0);

    //创建vo对象
    DrivingLineVo drivingLineVo = new DrivingLineVo();
    //预估时间
    drivingLineVo.setDuration(route.getBigDecimal("duration"));
    //距离 6.583 == 6.58 / 6.59 drivingLineVo.setDistance(route.getBigDecimal("distance")
    .divide(new BigDecimal(1000))
    .setScale(2, RoundingMode.HALF_UP));
    //路线
    drivingLineVo.setPolyline(route.getJSONArray("polyline"));

    return drivingLineVo;
    }
    }

预估订单金额

整合规则引擎Drools

  • 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <dependencies>
    <dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    </dependency>
    <dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    </dependency>
    <dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-decisiontables</artifactId>
    </dependency>
    <dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-mvel</artifactId>
    </dependency>
    </dependencies>
  • 创建Drools配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Configuration
    public class DroolsConfig {
    // 制定规则文件的路径
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";

    @Bean
    public KieContainer kieContainer() {
    KieServices kieServices = KieServices.Factory.get();

    KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
    kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
    KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
    kb.buildAll();

    KieModule kieModule = kb.getKieModule();
    KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
    return kieContainer;
    }
    }
  • 创建规则文件

封装费用规则接口

实体类

  • 两个实体类,输入对象和输出对象

  • 输入对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Data
    public class FeeRuleRequest {

    @Schema(description = "代驾里程")// swagger里面的描述
    private BigDecimal distance;

    @Schema(description = "代驾时间")
    private String startTime;

    @Schema(description = "等候分钟")
    private Integer waitMinute;
    }
  • 输出对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    @Data
    public class FeeRuleResponse {


    @Schema(description = "总金额")
    private BigDecimal totalAmount;

    @Schema(description = "里程费")
    private BigDecimal distanceFee;

    @Schema(description = "等时费用")
    private BigDecimal waitFee;

    @Schema(description = "远程费")
    private BigDecimal longDistanceFee;

    @Schema(description = "基础里程(公里)")
    private BigDecimal baseDistance;

    @Schema(description = "基础里程费(元)")
    private BigDecimal baseDistanceFee;

    @Schema(description = "超出基础里程的里程(公里)")
    private BigDecimal exceedDistance;

    @Schema(description = "超出基础里程的价格(元/公里)")
    private BigDecimal exceedDistancePrice;

    @Schema(description = "基础等时分钟(分钟)")
    private Integer baseWaitMinute;

    @Schema(description = "超出基础等时的分钟(分钟)")
    private Integer exceedWaitMinute;

    @Schema(description = "超出基础分钟的价格(元/分钟)")
    private BigDecimal exceedWaitMinutePrice;

    @Schema(description = "基础远途里程(公里)")
    private BigDecimal baseLongDistance;

    @Schema(description = "超出基础远程里程的里程(公里)")
    private BigDecimal exceedLongDistance;

    @Schema(description = "超出基础远程里程的价格(元/公里)")
    private BigDecimal exceedLongDistancePrice;
    }

费用规则文件

1.起步价
00:00:00-06:59:59 19元(含3公里)
07:00:00-23:59:59 19元(含5公里)
2.里程费
超出起步里程后开始计算
00:00:00-06:59:59 4元/1公里
07:00:00-23:59:59 3元/1公里
3.等候费
等候10分钟后 1元/1分钟
4.远途费
订单行程超出12公里后每公里1元
5.计算总金额
订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package com.atguigu.daijia

import com.atguigu.daijia.model.form.rules.FeeRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

global com.atguigu.daijia.model.vo.rules.FeeRuleResponse feeRuleResponse;

/**
1.起步价
00:00:00-06:59:59 19元(含3公里)
07:00:00-23:59:59 19元(含5公里)
*/
rule "起步价 00:00:00-06:59:59 19元(含3公里)"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(startTime >= "00:00:00" && startTime <= "06:59:59")
then
feeRuleResponse.setBaseDistance(new BigDecimal("3.0"));
feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));
//3公里内里程费为0
feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
System.out.println("00:00:00-06:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end
rule "起步价 07:00:00-23:59:59 19元(含5公里)"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(startTime >= "07:00:00" && startTime <= "23:59:59")
then
feeRuleResponse.setBaseDistance(new BigDecimal("5.0"));
feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));

//5公里内里程费为0
feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
System.out.println("07:00:00-23:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end

/**
2.里程费
超出起步里程后开始计算
00:00:00-06:59:59 4元/1公里
07:00:00-23:59:59 3元/1公里
*/
rule "里程费 00:00:00-06:59:59 4元/1公里"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(startTime >= "00:00:00"
&& startTime <= "06:59:59"
&& distance.doubleValue() > 3.0)
then
BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("3.0"));
feeRuleResponse.setExceedDistance(exceedDistance);
feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end
rule "里程费 07:00:00-23:59:59 3元/1公里"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(startTime >= "07:00:00"
&& startTime <= "23:59:59"
&& distance.doubleValue() > 5.0)
then
BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("5.0"));
feeRuleResponse.setExceedDistance(exceedDistance);
feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end

/**
3.等候费
等候10分钟后 1元/1分钟
*/
rule "等候费 等候10分钟后 1元/1分钟"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(waitMinute > 10)
then
Integer exceedWaitMinute = $rule.getWaitMinute() - 10;
feeRuleResponse.setBaseWaitMinute(10);
feeRuleResponse.setExceedWaitMinute(exceedWaitMinute);
feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
System.out.println("等候费,超出分钟:" + feeRuleResponse.getExceedWaitMinute() + "分钟,单价:" + feeRuleResponse.getExceedWaitMinutePrice());
end
rule "无等候费"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(waitMinute <= 10)
then
feeRuleResponse.setBaseWaitMinute(10);
feeRuleResponse.setExceedWaitMinute(0);
feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
System.out.println("等候费:无");
end

/**
4.远途费
订单行程超出12公里后每公里1元
*/
rule "远途费 订单行程超出12公里后每公里1元"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(distance.doubleValue() > 12.0)
then
BigDecimal exceedLongDistance = $rule.getDistance().subtract(new BigDecimal("12.0"));
feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
feeRuleResponse.setExceedLongDistance(exceedLongDistance);
feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("1.0"));
System.out.println("远途费,超出公里:" + feeRuleResponse.getExceedLongDistance() + "公里,单价:" + feeRuleResponse.getExceedLongDistancePrice());
end
rule "无远途费"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(distance.doubleValue() <= 12.0)
then
feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
feeRuleResponse.setExceedLongDistance(new BigDecimal("0"));
feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("0"));
System.out.println("远途费:无");
end

/**
5.计算总金额
订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
*/
rule "计算总金额"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(distance.doubleValue() > 0.0)
then
//订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
BigDecimal distanceFee = feeRuleResponse.getBaseDistanceFee().add(feeRuleResponse.getExceedDistance().multiply(feeRuleResponse.getExceedDistancePrice()));
BigDecimal waitFee = new BigDecimal(feeRuleResponse.getExceedWaitMinute()).multiply(feeRuleResponse.getExceedWaitMinutePrice());
BigDecimal longDistanceFee = feeRuleResponse.getExceedLongDistance().multiply(feeRuleResponse.getExceedLongDistancePrice());
BigDecimal totalAmount = distanceFee.add(waitFee).add(longDistanceFee);
feeRuleResponse.setDistanceFee(distanceFee);
feeRuleResponse.setWaitFee(waitFee);
feeRuleResponse.setLongDistanceFee(longDistanceFee);
feeRuleResponse.setTotalAmount(totalAmount);
System.out.println("计算总金额:" + feeRuleResponse.getTotalAmount() + "元");
end

预估订单金额接口

  • FeeRuleController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Slf4j
    @RestController
    @RequestMapping("/rules/fee")
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class FeeRuleController {

    @Autowired
    private FeeRuleService feeRuleService;
    @Operation(summary = "计算订单费用")
    @PostMapping("/calculateOrderFee")
    public Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm) {
    FeeRuleResponseVo feeRuleResponseVo = feeRuleService.calculateOrderFee(calculateOrderFeeForm);
    return Result.ok(feeRuleResponseVo);
    }
    }
  • FeeRuleService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @Slf4j
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class FeeRuleServiceImpl implements FeeRuleService {

    @Autowired
    private KieContainer kieContainer;

    // 计算订单费用
    @Override
    public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm form) {
    // 封装输入对象
    FeeRuleRequest request = new FeeRuleRequest();
    request.setDistance(form.getDistance());
    Date startTime = form.getStartTime();
    request.setStartTime(new DateTime(startTime).toString("HH:mm:ss"));
    request.setWaitMinute(form.getWaitMinute());

    // Drools执行规则
    KieSession session = kieContainer.newKieSession();
    // 封装返回对象
    FeeRuleResponseVo response = new FeeRuleResponseVo();
    session.setGlobal("feeRuleResponse",response);

    session.insert(request);
    session.fireAllRules();
    session.dispose();

    // 封装数据到输出对象
    FeeRuleResponseVo vo = new FeeRuleResponseVo();
    BeanUtils.copyProperties(response,vo);
    return vo;
    }
    }

远程调用接口

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(value = "service-rules")
public interface FeeRuleFeignClient {

/**
* 计算订单费用
* @param calculateOrderFeeForm
* @return
*/
@PostMapping("/rules/fee/calculateOrderFee")
Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm);
}

预估订单数据接口

  • OrderController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Autowired  
    private OrderService orderService;

    @Operation(summary = "预估订单数据")
    @LoginDetection
    @PostMapping("/expectOrder")
    public Result<ExpectOrderVo> expectOrder(@RequestBody ExpectOrderForm expectOrderForm) {
    return Result.ok(orderService.expectOrder(expectOrderForm));
    }
  • OrderService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @Slf4j
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class OrderServiceImpl implements OrderService {

    @Autowired
    private MapFeignClient mapFeignClient;

    @Autowired
    private FeeRuleFeignClient feeRuleFeignClient;

    // 预估订单数据
    @Override
    public ExpectOrderVo expectOrder(ExpectOrderForm expectOrderForm) {
    //获取驾驶线路
    CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
    BeanUtils.copyProperties(expectOrderForm,calculateDrivingLineForm);
    Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
    DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

    //获取订单费用
    FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
    calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
    calculateOrderFeeForm.setStartTime(new Date());
    calculateOrderFeeForm.setWaitMinute(0);
    Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
    FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

    //封装ExpectOrderVo
    ExpectOrderVo expectOrderVo = new ExpectOrderVo();
    expectOrderVo.setDrivingLineVo(drivingLineVo);
    expectOrderVo.setFeeRuleResponseVo(feeRuleResponseVo);
    return expectOrderVo;
    }
    }

乘客下单(一)

点击 呼叫代驾 会生成代驾订单,在 order_info 里添加订单数据

乘客下单接口

  • controller 不写了,就是调用获取数据

  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Service
    @SuppressWarnings({"unchecked", "rawtypes"})
    public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderStatusLogMapper orderStatusLogMapper;

    // 乘客下单
    @Override
    public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
    // 向order_info表中插入数据
    OrderInfo orderInfo = new OrderInfo();
    BeanUtils.copyProperties(orderInfoForm, orderInfo);

    orderInfo.setOrderNo(UUID.randomUUID().toString().replaceAll("-", ""));
    orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());

    orderInfoMapper.insert(orderInfo);

    // 记录日志
    this.log(orderInfo.getId(), orderInfo.getStatus());

    return orderInfo.getId();
    }

    public void log(Long orderId, Integer status) {
    OrderStatusLog orderStatusLog = new OrderStatusLog();
    orderStatusLog.setOrderId(orderId);
    orderStatusLog.setOrderStatus(status);
    orderStatusLog.setOperateTime(new Date());
    orderStatusLogMapper.insert(orderStatusLog);
    }
    }
  • 远程调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @FeignClient(value = "service-order")
    public interface OrderInfoFeignClient {

    /**
    * 保存订单信息
    * @param orderInfoForm
    * @return
    */
    @PostMapping("/order/info/saveOrderInfo")
    Result<Long> saveOrderInfo(@RequestBody OrderInfoForm orderInfoForm);
    }
  • web-service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Override
    public Long submitOrder(SubmitOrderForm submitOrderForm) {
    //1 重新计算驾驶线路
    CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
    BeanUtils.copyProperties(submitOrderForm,submitOrderForm);
    Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
    DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

    //2 重新订单费用
    FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
    calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
    calculateOrderFeeForm.setStartTime(new Date());
    calculateOrderFeeForm.setWaitMinute(0);
    Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
    FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

    //封装数据
    OrderInfoForm orderInfoForm = new OrderInfoForm();
    BeanUtils.copyProperties(submitOrderForm,orderInfoForm);
    orderInfoForm.setExpectDistance(drivingLineVo.getDistance());
    orderInfoForm.setExpectAmount(feeRuleResponseVo.getTotalAmount());
    Result<Long> orderInfoResult = orderInfoFeignClient.saveOrderInfo(orderInfoForm);
    Long orderId = orderInfoResult.getData();

    //TODO 查询附近可以接单司机

    return orderId;
    }

查询订单状态

订单微服务模块接口

  • 根据订单id得到订单状态

  • OrderInfoController

    1
    2
    3
    4
    5
    @Operation(summary = "根据订单id获取订单状态")
    @GetMapping("/getOrderStatus/{orderId}")
    public Result<Integer> getOrderStatus(@PathVariable Long orderId) {
    return Result.ok(orderInfoService.getOrderStatus(orderId));
    }
  • service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override  
    public Integer getOrderStatus(Long orderId) {
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(OrderInfo::getId, orderId);
    wrapper.select(OrderInfo::getStatus);

    OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);

    if(orderInfo == null){
    return OrderStatus.NULL_ORDER.getStatus();
    }
    return orderInfo.getStatus();
    }
  • 远程调用

    1
    2
    3
    4
    5
    6
    7
    /**
    * 根据订单id获取订单状态
    * @param orderId
    * @return
    */
    @GetMapping("/order/info/getOrderStatus/{orderId}")
    Result<Integer> getOrderStatus(@PathVariable("orderId") Long orderId);

远程调用

乘客端 web-customer

controller

1
2
3
4
5
6
@Operation(summary = "查询订单状态")  
@LoginDetection
@GetMapping("/getOrderStatus/{orderId}")
public Result<Integer> getOrderStatus(@PathVariable Long orderId) {
return Result.ok(orderService.getOrderStatus(orderId));
}

service

1
2
3
4
5
@Override
public Integer getOrderStatus(Long orderId) {
Result<Integer> integerResult = orderInfoFeignClient.getOrderStatus(orderId);
return integerResult.getData();
}

司机端 web-driver

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Tag(name = "订单API接口管理")
@RestController
@RequestMapping("/order")
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderController {

@Autowired
private OrderService orderService;

@Operation(summary = "查询订单状态")
@GuiguLogin
@GetMapping("/getOrderStatus/{orderId}")
public Result<Integer> getOrderStatus(@PathVariable Long orderId) {
return Result.ok(orderService.getOrderStatus(orderId));
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderServiceImpl implements OrderService {

@Autowired
private OrderInfoFeignClient orderInfoFeignClient;

@Override
public Integer getOrderStatus(Long orderId) {
return orderInfoFeignClient.getOrderStatus(orderId).getData();
}
}

乘客下单(二)

搜索附件可以下单的司机

Redis的GEO功能

  • **GEOADD 添加位置信息
1
GEOADD zhangsan 116.403963 39.915119 tiananmen 116.417876 39.915411 wangfujing 116.404354 39.904748 qianmen
  • **GEORADIUS 搜索范围以内消息
1
GEORADIUS zhangsan 116.4000 39.9000 1 km WITHDIST

实时更新司机的位置信息

封装位置相关接口

service.service-map

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Tag(name = "位置API接口管理")
@RestController
@RequestMapping("/map/location")
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationController {

@Autowired
private LocationService locationService;

//司机开启接单,更新司机位置信息
@Operation(summary = "开启接单服务:更新司机经纬度位置")
@PostMapping("/updateDriverLocation")
public Result<Boolean> updateDriverLocation(@RequestBody
UpdateDriverLocationForm updateDriverLocationForm) {
Boolean flag = locationService.updateDriverLocation(updateDriverLocationForm);
return Result.ok(flag);
}

//司机关闭接单,删除司机位置信息
@Operation(summary = "关闭接单服务:删除司机经纬度位置")
@DeleteMapping("/removeDriverLocation/{driverId}")
public Result<Boolean> removeDriverLocation(@PathVariable Long driverId) {
return Result.ok(locationService.removeDriverLocation(driverId));
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

@Autowired
private RedisTemplate redisTemplate;

// 更新司机位置信息
@Override
public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
updateDriverLocationForm.getLatitude().doubleValue());
redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION,
point,
updateDriverLocationForm.getDriverId().toString());
return true;
}

// 删除司机位置信息
@Override
public Boolean removeDriverLocation(Long driverId) {
redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION,driverId.toString());
return true;
}
}

远程调用定义

service-client.service-map-client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FeignClient(value = "service-map")
public interface LocationFeignClient {

/**
* 开启接单服务:更新司机经纬度位置
* @param updateDriverLocationForm
* @return
*/
@PostMapping("/map/location/updateDriverLocation")
Result<Boolean> updateDriverLocation(@RequestBody UpdateDriverLocationForm updateDriverLocationForm);

/**
* 关闭接单服务:删除司机经纬度位置
* @param driverId
* @return
*/
@DeleteMapping("/map/location/removeDriverLocation/{driverId}")
Result<Boolean> removeDriverLocation(@PathVariable("driverId") Long driverId);
}

司机web端

web.web-driver.controller

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j  
@Tag(name = "位置API接口管理")
@RestController
@RequestMapping(value="/location")
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationController {

@Autowired
private LocationService locationService;

@Operation(summary = "开启接单服务:更新司机经纬度位置")
@LoginDetection
@PostMapping("/updateDriverLocation")
public Result<Boolean> updateDriverLocation(@RequestBody UpdateDriverLocationForm updateDriverLocationForm) {
Long driverId = AuthContextHolder.getUserId();
updateDriverLocationForm.setDriverId(driverId);
return Result.ok(locationService.updateDriverLocation(updateDriverLocationForm));
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

@Autowired
private LocationFeignClient locationFeignClient;

// 更新司机消息
@Override
public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
Result<Boolean> booleanResult = locationFeignClient.updateDriverLocation(updateDriverLocationForm);
return booleanResult.getData();
}
}

获取司机个性化设置信息

封装查询司机个性化信息接口

service.service-driver.driver

controller

1
2
3
4
5
@Operation(summary = "获取司机设置信息")
@GetMapping("/getDriverSet/{driverId}")
public Result<DriverSet> getDriverSet(@PathVariable Long driverId) {
return Result.ok(driverInfoService.getDriverSet(driverId));
}

service

1
2
3
4
5
6
7
@Override
public DriverSet getDriverSet(Long driverId) {
LambdaQueryWrapper<DriverSet> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DriverSet::getDriverId, driverId);
DriverSet driverSet = driverSetMapper.selectOne(wrapper);
return driverSet;
}

远程调用

service-client.serivce-driver-client.DriverInfoFeignClient

1
2
@GetMapping("/driver/info/getDriverSet/{driverId}")  
Result<DriverSet> getDriverSet(@PathVariable("driverId") Long driverId);

修改司机web端

web.web-driver.controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {

@Autowired
private LocationFeignClient locationFeignClient;

@Autowired
private DriverInfoFeignClient driverInfoFeignClient;

// 更新司机消息
@Override
public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
Long driverId = updateDriverLocationForm.getDriverId();
Result<DriverSet> driverSet = driverInfoFeignClient.getDriverSet(driverId);
DriverSet data = driverSet.getData();

if(data.getServiceStatus() == 1){
Result<Boolean> booleanResult = locationFeignClient.updateDriverLocation(updateDriverLocationForm);
return booleanResult.getData();
}else {
throw new GuiguException(ResultCodeEnum.NO_START_SERVICE);
}
}
}

搜索附件适合接单的司机

封装搜索接单司机接口

service-map.map

controller

1
2
3
4
5
6
@Operation(summary = "搜索附近满足条件的司机")
@PostMapping("/searchNearByDriver")
public Result<List<NearByDriverVo>> searchNearByDriver(@RequestBody
SearchNearByDriverForm searchNearByDriverForm) {
return Result.ok(locationService.searchNearByDriver(searchNearByDriverForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//搜索附近满足条件的司机
@Override
public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
//搜索经纬度位置5公里以内的司机
//1 操作redis里面geo
//创建point,经纬度位置
Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
searchNearByDriverForm.getLatitude().doubleValue());

//定义距离,5公里
Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS,
RedisGeoCommands.DistanceUnit.KILOMETERS);

//创建circle对象,point distance
Circle circle = new Circle(point,distance);

//定义GEO参数,设置返回结果包含内容
RedisGeoCommands.GeoRadiusCommandArgs args =
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance() //包含距离
.includeCoordinates() //包含坐标
.sortAscending(); //升序

GeoResults<RedisGeoCommands.GeoLocation<String>> result =
redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);

//2 查询redis最终返回list集合
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();

//3 对查询list集合进行处理
// 遍历list集合,得到每个司机信息
// 根据每个司机个性化设置信息判断
List<NearByDriverVo> list = new ArrayList<>();
if(!CollectionUtils.isEmpty(content)) {
Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
while(iterator.hasNext()) {
GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();

//获取司机id
Long driverId = Long.parseLong(item.getContent().getName());

//远程调用,根据司机id个性化设置信息
Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
DriverSet driverSet = driverSetResult.getData();

//判断订单里程order_distance
BigDecimal orderDistance = driverSet.getOrderDistance();
//orderDistance==0,司机没有限制的
//如果不等于0 ,比如30,接单30公里代驾订单。
//接单距离 - 当前单子距离 < 0,不复合条件
// 30 35
if(orderDistance.doubleValue() != 0
&& orderDistance.subtract(searchNearByDriverForm.getMileageDistance()).doubleValue()<0) {
continue;
}

//判断接单里程 accept_distance
//当前接单距离
BigDecimal currentDistance =
new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);

BigDecimal acceptDistance = driverSet.getAcceptDistance();
if(acceptDistance.doubleValue() !=0
&& acceptDistance.subtract(currentDistance).doubleValue()<0) {
continue;
}

//封装复合条件数据
NearByDriverVo nearByDriverVo = new NearByDriverVo();
nearByDriverVo.setDriverId(driverId);
nearByDriverVo.setDistance(currentDistance);
list.add(nearByDriverVo);

}

}
return list;
}

接口测试

  • 启动相关服务

service-map 和 service-driver

  • 使用Swagger进行接口测试

http://localhost:8503/doc.html#/home

第一个接口测试用例及结果
![[Pasted image 20250528233022.png]]

发现错误来源于redis,开始排查

排查成功,犯了两个很低级的错误

  • 测试错误接口
  • nacos没有改配置文件,导致redis连接不上

再次测试为成功
![[Pasted image 20250528233937.png]]

第二个接口测试用例及结果
![[Pasted image 20250528234733.png]]

还是失败,挠头了,去看看报错是什么
![[Pasted image 20250528235311.png]]

空对象?ok啊,结合自己聪明的大脑以及一点外界帮助,得出原因是因为司机只有一个,但是却有三满足条件司机的数据,程序蒙了,不知道返回哪个

解决方法是:因为是测试嘛,也不用太严谨,自己手动在数据库里面凭空产生点司机出来就行

再次测试,成功
![[Pasted image 20250528235542.png]]

集成XXL-JOB

service.serivce-dispatch模块

导入依赖

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
</dependencies>

执行器配置

  • 修改nacos配置文件 service-dispatch-dev.yaml

  • 创建XXL-JOB配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;



    @Configuration

    public class XxlJobConfig {

        private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

        @Value("${xxl.job.admin.addresses}")
        private String adminAddresses;

        @Value("${xxl.job.accessToken}")
        private String accessToken;

        @Value("${xxl.job.executor.appname}")
        private String appname;

        @Value("${xxl.job.executor.address}")
        private String address;

        @Value("${xxl.job.executor.ip}")
        private String ip;

        @Value("${xxl.job.executor.port}")
        private int port;

        @Value("${xxl.job.executor.logpath}")
        private String logPath;

        @Value("${xxl.job.executor.logretentiondays}")
        private int logRetentionDays;

        @Bean
        public XxlJobSpringExecutor xxlJobExecutor() {
            logger.info(">>>>>>>>>>> xxl-job config init.");
            XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
            xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
            xxlJobSpringExecutor.setAppname(appname);
            xxlJobSpringExecutor.setAddress(address);
            xxlJobSpringExecutor.setIp(ip);
            xxlJobSpringExecutor.setPort(port);
            xxlJobSpringExecutor.setAccessToken(accessToken);
            xxlJobSpringExecutor.setLogPath(logPath);
            xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

            return xxlJobSpringExecutor;
        }
    }

编写任务job方法测试是否成功

1
2
3
4
5
6
7
8
@Component
public class DispatchJobHandler {

@XxlJob("firstJobHandler")
public void testJobHandler() {
System.out.println("xxl-job项目集成测试");
}
}

测试

第一步,启动相关服务

  • 调度中心
  • 执行器项目服务

第二步,在调度中心创建任务

第三步,启动任务

测试的时候遇到点小问题:

  • 先运行调度中心再运行项目
  • 失败要留意xxl的错误日志,哪里有问题就删哪里

封装XXL-JOB客户端

调度中心创建任务方法

在xxl-job-admin中的controller中创建

JobInfoController把原来的添加删除修改任务替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//自定义任务操作的方法
//添加任务
@RequestMapping("/addJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addJobInfo(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.add(jobInfo);
}

//删除任务
@RequestMapping("/removeJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> removeJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.remove(jobInfo.getId());
}

//修改任务
@RequestMapping("/updateJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> updateJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.update(jobInfo);
}

//停止任务
@RequestMapping("/stopJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> pauseJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.stop(jobInfo.getId());
}

//启动任务
@RequestMapping("/startJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> startJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.start(jobInfo.getId());
}

//添加并启动任务
@RequestMapping("/addAndStartJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addAndStartJob(@RequestBody XxlJobInfo jobInfo) {
ReturnT<String> result = xxlJobService.add(jobInfo);

String content = result.getContent();
int id = Integer.parseInt(content);
xxlJobService.start(id);

//立即执行一次
JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, jobInfo.getExecutorParam(), "");
return result;
}

执行器项目配置文件添加任务方法

  • 修改nacos配置文件 service-dispatch-dev.yaml
  • 在job.admin下
    1
    2
    3
    4
    5
    6
    7
    client:
    jobGroupId: 1
    addUrl: ${xxl.job.admin.addresses}/jobinfo/addJob
    removeUrl: ${xxl.job.admin.addresses}/jobinfo/removeJob
    startJobUrl: ${xxl.job.admin.addresses}/jobinfo/startJob
    stopJobUrl: ${xxl.job.admin.addresses}/jobinfo/stopJob
    addAndStartUrl: ${xxl.job.admin.addresses}/jobinfo/addAndStartJob

添加相关配置类service-dispatch

  • 创建配置类,读取配置文件里面的调用的调度中心的操作方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Data
    @Component
    @ConfigurationProperties(prefix = "xxl.job.client")
    public class XxlJobClientConfig {

    private Integer jobGroupId;
    private String addUrl;
    private String removeUrl;
    private String startJobUrl;
    private String stopJobUrl;
    private String addAndStartUrl;
    }
  • 创建客户端类,编写调用调度中心里面的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    import com.alibaba.fastjson.JSONObject;
    import com.atguigu.daijia.common.execption.GuiguException;
    import com.atguigu.daijia.common.result.ResultCodeEnum;
    import com.atguigu.daijia.dispatch.xxl.config.XxlJobClientConfig;
    import com.atguigu.daijia.model.entity.dispatch.XxlJobInfo;
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.RestTemplate;

    @Slf4j
    @Component
    public class XxlJobClient {

    @Autowired
    private XxlJobClientConfig xxlJobClientConfig;

    //客户端调用服务端里面的方法
    @Autowired
    private RestTemplate restTemplate;

    @SneakyThrows
    public Long addJob(String executorHandler, String param, String corn, String desc){
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
    xxlJobInfo.setJobDesc(desc);
    xxlJobInfo.setAuthor("qy");
    xxlJobInfo.setScheduleType("CRON");
    xxlJobInfo.setScheduleConf(corn);
    xxlJobInfo.setGlueType("BEAN");
    xxlJobInfo.setExecutorHandler(executorHandler);
    xxlJobInfo.setExecutorParam(param);
    xxlJobInfo.setExecutorRouteStrategy("FIRST");
    xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
    xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
    xxlJobInfo.setExecutorTimeout(0);
    xxlJobInfo.setExecutorFailRetryCount(0);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

    String url = xxlJobClientConfig.getAddUrl();
    ResponseEntity<JSONObject> response =
    restTemplate.postForEntity(url, request, JSONObject.class);

    if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
    log.info("增加xxl执行任务成功,返回信息:{}", response.getBody().toJSONString());
    //content为任务id
    return response.getBody().getLong("content");
    }
    log.info("调用xxl增加执行任务失败:{}", response.getBody().toJSONString());
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean startJob(Long jobId) {
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setId(jobId.intValue());

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

    String url = xxlJobClientConfig.getStartJobUrl();
    ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
    if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
    log.info("启动xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
    return true;
    }
    log.info("启动xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean stopJob(Long jobId) {
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setId(jobId.intValue());

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

    String url = xxlJobClientConfig.getStopJobUrl();
    ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
    if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
    log.info("停止xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
    return true;
    }
    log.info("停止xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean removeJob(Long jobId) {
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setId(jobId.intValue());

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

    String url = xxlJobClientConfig.getRemoveUrl();
    ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
    if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
    log.info("删除xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
    return true;
    }
    log.info("删除xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    //添加并启动任务
    public Long addAndStart(String executorHandler, String param, String corn, String desc) {
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
    xxlJobInfo.setJobDesc(desc);
    xxlJobInfo.setAuthor("qy");
    xxlJobInfo.setScheduleType("CRON");
    xxlJobInfo.setScheduleConf(corn);
    xxlJobInfo.setGlueType("BEAN");
    xxlJobInfo.setExecutorHandler(executorHandler);
    xxlJobInfo.setExecutorParam(param);
    xxlJobInfo.setExecutorRouteStrategy("FIRST");
    xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
    xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
    xxlJobInfo.setExecutorTimeout(0);
    xxlJobInfo.setExecutorFailRetryCount(0);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

    //获取调度中心请求路径
    String url = xxlJobClientConfig.getAddAndStartUrl();

    //restTemplate
    ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
    if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
    log.info("增加并开始执行xxl任务成功,返回信息:{}", response.getBody().toJSONString());
    //content为任务id
    return response.getBody().getLong("content");
    }
    log.info("增加并开始执行xxl任务失败:{}", response.getBody().toJSONString());
    throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
    }

启动类配置RestTemplate

直接把RestTemplate写在启动类里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication  
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceDispatchApplication {

public static void main(String[] args) {
SpringApplication.run(ServiceDispatchApplication.class, args);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

创建并启动任务接口

service-dispatch

controller

1
2
3
4
5
6
7
// 创建并启动任务调度方法
@Operation(summary = "添加并开始新订单任务调度")
@PostMapping("/addAndStartTask")
public Result<Long> addAndStartTask(@RequestBody NewOrderTaskVo newOrderTaskVo) {
Long id = newOrderService.addAndStartTask(newOrderTaskVo);
return Result.ok(id);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 添加并开始新订单任务调度
@Override
public Long addAndStartTask(NewOrderTaskVo newOrderTaskVo) {
// 判断当前订单是否开启任务调度
LambdaQueryWrapper<OrderJob> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderJob::getOrderId, newOrderTaskVo.getOrderId());
OrderJob orderJob = orderJobMapper.selectOne(wrapper);

// 没有启动,进行操作
if(orderJob == null){
//创建并启动任务调度
//String executorHandler 执行任务job方法
// String param
// String corn 执行cron表达式
// String desc 描述信息
Long id = xxlJobClient.addAndStart("newOrderTaskHandler",
"", "0 0/1 * * * ?",
"新创建订单任务调度:" + newOrderTaskVo.getOrderId());

orderJob = new OrderJob();
orderJob.setOrderId(newOrderTaskVo.getOrderId());
orderJob.setJobId(id);
orderJob.setParameter(JSONObject.toJSONString(newOrderTaskVo));

orderJobMapper.insert(orderJob);
}

return orderJob.getJobId();
}

远程调用

service-client
service-dispatch-client

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(value = "service-dispatch")
public interface NewOrderFeignClient {

/**
* 添加新订单任务
* @param newOrderDispatchVo
* @return
*/
@PostMapping("/dispatch/newOrder/addAndStartTask")
Result<Long> addAndStartTask(@RequestBody NewOrderTaskVo newOrderDispatchVo);
}

开发具体任务job方法

JobHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class JobHandler {

@Autowired
private XxlJobLogMapper xxlJobLogMapper;

@Autowired
private NewOrderService newOrderService;

@XxlJob("newOrderTaskHandler")
public void newOrderTaskHandler() {

//记录任务调度日志
XxlJobLog xxlJobLog = new XxlJobLog();
xxlJobLog.setJobId(XxlJobHelper.getJobId());
long startTime = System.currentTimeMillis();

try {
//执行任务:搜索附近代驾司机
newOrderService.executeTask(XxlJobHelper.getJobId());

//成功状态
xxlJobLog.setStatus(1);
} catch (Exception e) {
//失败状态
xxlJobLog.setStatus(0);
xxlJobLog.setError(e.getMessage());
e.printStackTrace();
} finally {
long times = System.currentTimeMillis()- startTime;
//TODO 完善long
xxlJobLog.setTimes((int)times);
xxlJobLogMapper.insert(xxlJobLog);
}
}
}
  • 远程调用
    service-driver-client
    LocationFeignClient
    1
    2
    3
    // 搜索附近满足条件的司机
    @PostMapping("/map/location/searchNearByDriver")
    public Result<List<NearByDriverVo>> searchNearByDriver(@RequestBody SearchNearByDriverForm searchNearByDriverForm);

service-dispatch
NewOrderServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Autowired
private LocationFeignClient locationFeignClient;

@Autowired
private OrderInfoFeignClient orderInfoFeignClient;

@Autowired
private RedisTemplate redisTemplate;

// 搜索附件司机
@Override
public void executeTask(long jobId) {
// 根据jobid查询当前任务是否已经创建
LambdaQueryWrapper<OrderJob> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderJob::getJobId, jobId);
OrderJob orderJob = orderJobMapper.selectOne(wrapper);
if(orderJob == null){
return;
}
// 查询订单状态,如果是接单状态,继续执行,否则停止
String parameter = orderJob.getParameter();
NewOrderTaskVo newOrderTaskVo = JSONObject.parseObject(parameter, NewOrderTaskVo.class);
Integer status = orderInfoFeignClient.getOrderStatus(newOrderTaskVo.getOrderId()).getData();

if(status.intValue() != OrderStatus.WAITING_ACCEPT.getStatus().intValue()){
xxlJobClient.stopJob(jobId);
return;
}
// 远程调用,查询正在接单的司机集合
SearchNearByDriverForm from = new SearchNearByDriverForm();
SearchNearByDriverForm searchNearByDriverForm = new SearchNearByDriverForm();
searchNearByDriverForm.setLongitude(newOrderTaskVo.getStartPointLongitude());
searchNearByDriverForm.setLatitude(newOrderTaskVo.getStartPointLatitude());
List<NearByDriverVo> result = locationFeignClient.searchNearByDriver(from).getData();

// 遍历满足条件的司机集合,为每个司机创建临时队列,存储新订单信息
result.forEach(driver -> {
String repeatKey =
RedisConstant.DRIVER_ORDER_REPEAT_LIST+newOrderTaskVo.getOrderId();
Boolean isMember = redisTemplate.opsForSet().isMember(repeatKey, driver.getDriverId());
if(!isMember){
// 把订单信息推送刚给满足条件的司机
redisTemplate.opsForSet().add(parameter, driver.getDriverId());
// 设置过期时间15分钟(一分钟等待)
redisTemplate.expire(repeatKey,
RedisConstant.DRIVER_ORDER_REPEAT_LIST_EXPIRES_TIME,
TimeUnit.MINUTES);

NewOrderDataVo newOrderDataVo = new NewOrderDataVo();
newOrderDataVo.setOrderId(newOrderTaskVo.getOrderId());
newOrderDataVo.setStartLocation(newOrderTaskVo.getStartLocation());
newOrderDataVo.setEndLocation(newOrderTaskVo.getEndLocation());
newOrderDataVo.setExpectAmount(newOrderTaskVo.getExpectAmount());
newOrderDataVo.setExpectDistance(newOrderTaskVo.getExpectDistance());
newOrderDataVo.setExpectTime(newOrderTaskVo.getExpectTime());
newOrderDataVo.setFavourFee(newOrderTaskVo.getFavourFee());
newOrderDataVo.setDistance(driver.getDistance());
newOrderDataVo.setCreateTime(newOrderTaskVo.getCreateTime());
// 新订单保存司机的临时队列
String key = RedisConstant.DRIVER_ORDER_TEMP_LIST+driver.getDriverId();
redisTemplate.opsForList().leftPush(key, JSONObject.toJSONString(newOrderDataVo));
// 设置过期时间,一分钟
redisTemplate.expire(key, RedisConstant.DRIVER_ORDER_TEMP_LIST_EXPIRES_TIME, TimeUnit.MINUTES);
}
});
}

乘客下单添加任务调度

web-customer
补充之前OrderServiceImpl里写的查询附近可以接单司机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Autowired  
private NewOrderFeignClient newOrderFeignClient;

@Override
public Long submitOrder(SubmitOrderForm submitOrderForm) {
//1 重新计算驾驶线路
CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
BeanUtils.copyProperties(submitOrderForm,submitOrderForm);
Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
DrivingLineVo drivingLineVo = drivingLineVoResult.getData();

//2 重新订单费用
FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
calculateOrderFeeForm.setStartTime(new Date());
calculateOrderFeeForm.setWaitMinute(0);
Result<FeeRuleResponseVo> feeRuleResponseVoResult = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm);
FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoResult.getData();

//封装数据
OrderInfoForm orderInfoForm = new OrderInfoForm();
BeanUtils.copyProperties(submitOrderForm,orderInfoForm);
orderInfoForm.setExpectDistance(drivingLineVo.getDistance());
orderInfoForm.setExpectAmount(feeRuleResponseVo.getTotalAmount());
Result<Long> orderInfoResult = orderInfoFeignClient.saveOrderInfo(orderInfoForm);
Long orderId = orderInfoResult.getData();

// 任务调度:查询附近可以接单司机
NewOrderTaskVo newOrderTaskVo = new NewOrderTaskVo();
newOrderTaskVo.setOrderId(orderId);
newOrderTaskVo.setStartLocation(orderInfoForm.getStartLocation());
newOrderTaskVo.setStartPointLongitude(orderInfoForm.getStartPointLongitude());
newOrderTaskVo.setStartPointLatitude(orderInfoForm.getStartPointLatitude());
newOrderTaskVo.setEndLocation(orderInfoForm.getEndLocation());
newOrderTaskVo.setEndPointLongitude(orderInfoForm.getEndPointLongitude());
newOrderTaskVo.setEndPointLatitude(orderInfoForm.getEndPointLatitude());
newOrderTaskVo.setExpectAmount(orderInfoForm.getExpectAmount());
newOrderTaskVo.setExpectDistance(orderInfoForm.getExpectDistance());
newOrderTaskVo.setExpectTime(drivingLineVo.getDuration());
newOrderTaskVo.setFavourFee(orderInfoForm.getFavourFee());
newOrderTaskVo.setCreateTime(new Date());
Long jobId = newOrderFeignClient.addAndStartTask(newOrderTaskVo).getData();

return orderId;
}

司机获取最新订单数据

service-dispatch

查询最新订单和清空司机队列数据

NewOrderController

1
2
3
4
5
6
7
8
9
10
11
@Operation(summary = "查询司机新订单数据")
@GetMapping("/findNewOrderQueueData/{driverId}")
public Result<List<NewOrderDataVo>> findNewOrderQueueData(@PathVariable Long driverId) {
return Result.ok(newOrderService.findNewOrderQueueData(driverId));
}

@Operation(summary = "清空新订单队列数据")
@GetMapping("/clearNewOrderQueueData/{driverId}")
public Result<Boolean> clearNewOrderQueueData(@PathVariable Long driverId) {
return Result.ok(newOrderService.clearNewOrderQueueData(driverId));
}

NewOrderServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获取最新订单
@Override
public List<NewOrderDataVo> findNewOrderQueueData(Long driverId) {
List<NewOrderDataVo> list = new ArrayList<>();
String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
Long size = redisTemplate.opsForList().size(key);
if(size > 0) {
for (int i = 0; i < size; i++) {
String content = (String)redisTemplate.opsForList().leftPop(key);
NewOrderDataVo newOrderDataVo = JSONObject.parseObject(content,NewOrderDataVo.class);
list.add(newOrderDataVo);
}
}
return list;
}

//清空队列数据
@Override
public Boolean clearNewOrderQueueData(Long driverId) {
String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
redisTemplate.delete(key);
return true;
}

远程调用

service-dispatch

NewOrderFeignClient

1
2
3
4
5
6
7
// 查询司机新订单数据
@GetMapping("/dispatch/newOrder/findNewOrderQueueData/{driverId}")
Result<List<NewOrderDataVo>> findNewOrderQueueData(@PathVariable("driverId") Long driverId);

// 清空新订单队列数据
@GetMapping("/dispatch/newOrder/clearNewOrderQueueData/{driverId}")
Result<Boolean> clearNewOrderQueueData(@PathVariable("driverId") Long driverId);

司机web端调用

OrderController

1
2
3
4
5
6
7
@Operation(summary = "查询司机新订单数据")  
@LoginDetection
@GetMapping("/findNewOrderQueueData")
public Result<List<NewOrderDataVo>> findNewOrderQueueData() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.findNewOrderQueueData(driverId));
}

service

1
2
3
4
5
6
7
@Autowired  
private NewOrderFeignClient newOrderFeignClient;

@Override
public List<NewOrderDataVo> findNewOrderQueueData(Long driverId) {
return newOrderFeignClient.findNewOrderQueueData(driverId).getData();
}

司机接单

需求

  • 乘客下单后,新订单信息已经放在司机临时队列,然后可以开始接单了

首先,司机登录、认证(身份证、驾驶证、创建人脸模型)
第二,司机每天接单前都需要人脸识别
第三,司机开始接单,更新接单状态
第四,司机接单后,删除司机之前存储在redis里面的位置信息
第五,司机接单后,清空临时队列新订单信息

查找司机端当前订单

设定是接单去需要验证司机是否有未完成的订单,暂时不管

  • 后续完成,暂时跳过
1
2
3
4
5
6
7
8
9
@Operation(summary = "查找司机端当前订单")  
@LoginDetection
@GetMapping("/searchDriverCurrentOrder")
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder() {
CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
// TODO 后续完善
currentOrderInfoVo.setIsHasCurrentOrder(false);
return Result.ok(currentOrderInfoVo);
}

判断司机在当日是否人脸识别

service-driver

DriverInfoController

1
2
3
4
5
@Operation(summary = "判断司机当日是否进行过人脸识别")  
@GetMapping("/isFaceRecognition/{driverId}")
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId) {
return Result.ok(driverInfoService.isFaceRecognition(driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断司机当日是否进行过人脸识别  
@Override
public Boolean isFaceRecognition(Long driverId) {
//根据司机id + 当日日期进行查询
LambdaQueryWrapper<DriverFaceRecognition> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DriverFaceRecognition::getDriverId,driverId);
// 年-月-日 格式
wrapper.eq(DriverFaceRecognition::getFaceDate,new DateTime().toString("yyyy-MM-dd"));
//调用mapper方法
Long count = driverFaceRecognitionMapper.selectCount(wrapper);

return count != 0;
}

远程调用

DriverInfoFeignClient

1
2
3
// 判断司机当日是否进行过人脸识别  
@GetMapping("/driver/info/isFaceRecognition/{driverId}")
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId);

web端调用

DriverController

1
2
3
4
5
6
7
@Operation(summary = "判断司机当日是否进行过人脸识别")  
@LoginDetection
@GetMapping("/isFaceRecognition")
Result<Boolean> isFaceRecognition() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.isFaceRecognition(driverId));
}

service

1
2
3
4
5
6
// 判断司机当日是否进行过人脸识别  
@Override
public Boolean isFaceRecognition(Long driverId) {
Result<Boolean> faceRecognition = driverInfoFeignClient.isFaceRecognition(driverId);
return faceRecognition.getData();
}

人脸识别接口

service-driver

DriverInfoController

1
2
3
4
5
@Operation(summary = "验证司机人脸")
@PostMapping("/verifyDriverFace")
public Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm) {
return Result.ok(driverInfoService.verifyDriverFace(driverFaceModelForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 验证司机人脸  
@Override
public Boolean verifyDriverFace(DriverFaceModelForm driverFaceModelForm) {
// 照片比对
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性
// 以下代码示例仅供参考,建议采用更安全的方式来使用密钥
// 请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 使用临时密钥示例
// Credential cred = new Credential("SecretId", "SecretKey", "Token");
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("iai.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);

// 实例化要请求产品的client对象,clientProfile是可选的
IaiClient client = new IaiClient(cred,
tencentCloudProperties.getRegion(), clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
VerifyFaceRequest req = new VerifyFaceRequest();
// 设置相关参数
req.setImage(driverFaceModelForm.getImageBase64());
req.setPersonId(String.valueOf(driverFaceModelForm.getDriverId()));

// 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应
VerifyFaceResponse resp = client.VerifyFace(req);
// 输出json格式的字符串回包
System.out.println(AbstractModel.toJsonString(resp));
if(resp.getIsMatch()){
// 静态活体检测
Boolean isSuccess = this.detectLiveFace(driverFaceModelForm.getImageBase64());
if(isSuccess){
// 都没问题,添加塑胶到认证表中
DriverFaceRecognition driverFaceRecognition = new DriverFaceRecognition();
driverFaceRecognition.setDriverId(driverFaceModelForm.getDriverId());
driverFaceRecognition.setFaceDate(new Date());
driverFaceRecognitionMapper.insert(driverFaceRecognition);
return true;
}
}
} catch (TencentCloudSDKException e) {
System.out.println(e.toString());
}

throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}

//人脸静态活体检测
private Boolean detectLiveFace(String imageBase64) {
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("iai.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
IaiClient client = new IaiClient(cred, tencentCloudProperties.getRegion(),
clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
DetectLiveFaceRequest req = new DetectLiveFaceRequest();
req.setImage(imageBase64);
// 返回的resp是一个DetectLiveFaceResponse的实例,与请求对象对应
DetectLiveFaceResponse resp = client.DetectLiveFace(req);
// 输出json格式的字符串回包
System.out.println(DetectLiveFaceResponse.toJsonString(resp));
if(resp.getIsLiveness()) {
return true;
}
} catch (TencentCloudSDKException e) {
System.out.println(e.toString());
}
return false;
}

远程调用 service-driver-client

DriverInfoFeignClient

1
2
3
// 验证司机人脸  
@PostMapping("/driver/info/verifyDriverFace")
Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm);

web-driver

DriverController

1
2
3
4
5
6
7
@Operation(summary = "验证司机人脸")  
@LoginDetection
@PostMapping("/verifyDriverFace")
public Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm) {
driverFaceModelForm.setDriverId(AuthContextHolder.getUserId());
return Result.ok(driverService.verifyDriverFace(driverFaceModelForm));
}

service

1
2
3
4
5
6
// 验证司机人脸  
@Override
public Boolean verifyDriverFace(DriverFaceModelForm driverFaceModelForm) {
Result<Boolean> booleanResult = driverInfoFeignClient.verifyDriverFace(driverFaceModelForm);
return booleanResult.getData();
}

更新司机接单状态

DriverInfoController

1
2
3
4
5
@Operation(summary = "更新接单状态")  
@GetMapping("/updateServiceStatus/{driverId}/{status}")
public Result<Boolean> updateServiceStatus(@PathVariable Long driverId, @PathVariable Integer status) {
return Result.ok(driverInfoService.updateServiceStatus(driverId, status));
}

service

1
2
3
4
5
6
7
8
9
@Override  
public Boolean updateServiceStatus(Long driverId, Integer status) {
LambdaQueryWrapper<DriverSet> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DriverSet::getDriverId,driverId);
DriverSet driverSet = new DriverSet();
driverSet.setServiceStatus(status);
driverSetMapper.update(driverSet,wrapper);
return true;
}

远程调用DriverInfoFeignClient

1
2
3
// 更新接单状态  
@GetMapping("/driver/info/updateServiceStatus/{driverId}/{status}")
Result<Boolean> updateServiceStatus(@PathVariable("driverId") Long driverId, @PathVariable("status") Integer status);

开启接单服务web接口

在web-driver中进行编写

司机开启接单后,上传位置信息到redis的GEO,才能被任务调度搜索到司机信息,开始抢单

DriverController

1
2
3
4
5
6
7
@Operation(summary = "开始接单服务")  
@LoginDetection
@GetMapping("/startService")
public Result<Boolean> startService() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.startService(driverId));
}

DriverServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Autowired  
private LocationFeignClient locationFeignClient;

@Autowired
private NewOrderFeignClient newOrderFeignClient;

// 开始接单服务
@Override
public Boolean startService(Long driverId) {
//1 判断完成认证
DriverLoginVo driverLoginVo = driverInfoFeignClient.getDriverLoginInfo(driverId).getData();
if(driverLoginVo.getAuthStatus()!=2) {
throw new GuiguException(ResultCodeEnum.AUTH_ERROR);
}

//2 判断当日是否人脸识别
Boolean isFace = driverInfoFeignClient.isFaceRecognition(driverId).getData();
if(!isFace) {
throw new GuiguException(ResultCodeEnum.FACE_ERROR);
}

//3 更新订单状态 1 开始接单
driverInfoFeignClient.updateServiceStatus(driverId,1);

//4 删除redis司机位置信息
locationFeignClient.removeDriverLocation(driverId);

//5 清空司机临时队列数据
newOrderFeignClient.clearNewOrderQueueData(driverId);
return true;
}

停止接单服务web接口

DriverController

1
2
3
4
5
6
7
@Operation(summary = "停止接单服务")  
@LoginDetection
@GetMapping("/stopService")
public Result<Boolean> stopService() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.stopService(driverId));
}

DriverServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
//停止接单服务  
@Override
public Boolean stopService(Long driverId) {
//更新司机的接单状态 0
driverInfoFeignClient.updateServiceStatus(driverId,0);

//删除司机位置信息
locationFeignClient.removeDriverLocation(driverId);

//清空司机临时队列
newOrderFeignClient.clearNewOrderQueueData(driverId);
return true;
}

司机抢单

抢单接口

微服务抢单接口

service-order模块内

OrderInfoController

1
2
3
4
5
@Operation(summary = "司机抢单")  
@GetMapping("/robNewOrder/{driverId}/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long driverId, @PathVariable Long orderId) {
return Result.ok(orderInfoService.robNewOrder(driverId, orderId));
}

在OrderInfoServiceImpl之前保存订单 方法修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Autowired  
private RedisTemplate redisTemplate;

//乘客下单
@Override
public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
//order_info添加订单数据
OrderInfo orderInfo = new OrderInfo();
BeanUtils.copyProperties(orderInfoForm,orderInfo);
//订单号
String orderNo = UUID.randomUUID().toString().replaceAll("-","");
orderInfo.setOrderNo(orderNo);
//订单状态
orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());
orderInfoMapper.insert(orderInfo);

//记录日志
this.log(orderInfo.getId(),orderInfo.getStatus());

//向redis添加标识
//接单标识,标识不存在了说明不在等待接单状态了
redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
"0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);

return orderInfo.getId();
}

OrderInfoServiceImpl新增司机抢单方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//司机抢单  
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
//判断订单是否存在,通过Redis,减少数据库压力
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}

//司机抢单
//修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间
//修改条件:根据订单id
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
//设置
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setDriverId(driverId);
orderInfo.setAcceptTime(new Date());
//调用方法修改
int rows = orderInfoMapper.updateById(orderInfo);
if(rows != 1) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}

//删除抢单标识
redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
return true;
}

远程调用

1
2
3
4
5
6
7
8
/**  
* 司机抢单
* @param driverId
* @param orderId
* @return
*/
@GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")
Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId, @PathVariable("orderId") Long orderId);

司机web端接口

OrderController

1
2
3
4
5
6
7
@Operation(summary = "司机抢单")  
@LoginDetection
@GetMapping("/robNewOrder/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.robNewOrder(driverId, orderId));
}

service

1
2
3
4
5
6
// 司机抢单  
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
Result<Boolean> booleanResult = orderInfoFeignClient.robNewOrder(driverId, orderId);
return booleanResult.getData();
}

添加Redisson分布式锁到司机抢单

在service里service-order中OrderInfoServiceImpl添加分布式锁

修改之前写的robNewOrder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Autowired  
private RedissonClient redissonClient;

//司机抢单
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
//判断订单是否存在,通过Redis,减少数据库压力
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}

//创建锁
RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);

try {
//获取锁
boolean flag = lock.tryLock(RedisConstant.ROB_NEW_ORDER_LOCK_WAIT_TIME,
RedisConstant.ROB_NEW_ORDER_LOCK_LEASE_TIME,
TimeUnit.SECONDS);
if(flag) {
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//司机抢单
//修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间
//修改条件:根据订单id
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
//设置
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setDriverId(driverId);
orderInfo.setAcceptTime(new Date());
//调用方法修改
int rows = orderInfoMapper.updateById(orderInfo);
if(rows != 1) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}

//删除抢单标识
redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
}
}catch (Exception e) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}finally {
//释放
if(lock.isLocked()) {
lock.unlock();
}
}
return true;
}

订单执行(一)

加载当前订单

需求

  • 无论是司机端,还是乘客端,遇到页面切换,重新登录小程序等,只要回到首页面,查看当前是否有正在执行的订单,如果有跳转到当前订单的执行页面

乘客端查找当前订单

订单微服务接口

  • OrderInfoController
    1
    2
    3
    4
    5
    @Operation(summary = "乘客端查找当前订单")
    @GetMapping("/searchCustomerCurrentOrder/{customerId}")
    public Result<CurrentOrderInfoVo> searchCustomerCurrentOrder(@PathVariable Long customerId) {
    return Result.ok(orderInfoService.searchCustomerCurrentOrder(customerId));
    }

OrderInfoServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Autowired  
private RedissonClient redissonClient;

//乘客端查找当前订单
@Override
public CurrentOrderInfoVo searchCustomerCurrentOrder(Long customerId) {
//封装条件
//乘客id
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getCustomerId,customerId);

//各种状态
Integer[] statusArray = {
OrderStatus.ACCEPTED.getStatus(),
OrderStatus.DRIVER_ARRIVED.getStatus(),
OrderStatus.UPDATE_CART_INFO.getStatus(),
OrderStatus.START_SERVICE.getStatus(),
OrderStatus.END_SERVICE.getStatus(),
OrderStatus.UNPAID.getStatus()
};
wrapper.in(OrderInfo::getStatus,statusArray);

//获取最新一条记录
wrapper.orderByDesc(OrderInfo::getId);
wrapper.last(" limit 1");

//调用方法
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);

//封装到CurrentOrderInfoVo
CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
if(orderInfo != null) {
currentOrderInfoVo.setOrderId(orderInfo.getId());
currentOrderInfoVo.setStatus(orderInfo.getStatus());
currentOrderInfoVo.setIsHasCurrentOrder(true);
} else {
currentOrderInfoVo.setIsHasCurrentOrder(false);
}
return currentOrderInfoVo;
}

远程调用

serivce-order-client
OrderInfoFeignClient

1
2
3
4
5
6
7
/**  
* 乘客端查找当前订单
* @param customerId
* @return
*/
@GetMapping("/order/info/searchCustomerCurrentOrder/{customerId}")
Result<CurrentOrderInfoVo> searchCustomerCurrentOrder(@PathVariable("customerId") Long customerId);

乘客web端接口

web-customer
OrderController

1
2
3
4
5
6
7
@Operation(summary = "乘客端查找当前订单")  
@LoginDetection
@GetMapping("/searchCustomerCurrentOrder")
public Result<CurrentOrderInfoVo> searchCustomerCurrentOrder() {
Long customerId = AuthContextHolder.getUserId();
return Result.ok(orderService.searchCustomerCurrentOrder(customerId));
}

OrderServiceImpl

1
2
3
4
5
6
// 乘客端查找当前订单  
@Override
public CurrentOrderInfoVo searchCustomerCurrentOrder(Long customerId) {
Result<CurrentOrderInfoVo> currentOrderInfoVoResult = orderInfoFeignClient.searchCustomerCurrentOrder(customerId);
return currentOrderInfoVoResult.getData();
}

司机端查找当前订单

订单微服务接口

service-order
OrderInfoController

1
2
3
4
5
@Operation(summary = "司机端查找当前订单")
@GetMapping("/searchDriverCurrentOrder/{driverId}")
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder(@PathVariable Long driverId) {
return Result.ok(orderInfoService.searchDriverCurrentOrder(driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 司机端查找当前订单
@Override
public CurrentOrderInfoVo searchDriverCurrentOrder(Long driverId) {
//封装条件
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getDriverId,driverId);
Integer[] statusArray = {
OrderStatus.ACCEPTED.getStatus(),
OrderStatus.DRIVER_ARRIVED.getStatus(),
OrderStatus.UPDATE_CART_INFO.getStatus(),
OrderStatus.START_SERVICE.getStatus(),
OrderStatus.END_SERVICE.getStatus()
};
wrapper.in(OrderInfo::getStatus,statusArray);
wrapper.orderByDesc(OrderInfo::getId);
wrapper.last(" limit 1");
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
//封装到vo
CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
if(null != orderInfo) {
currentOrderInfoVo.setStatus(orderInfo.getStatus());
currentOrderInfoVo.setOrderId(orderInfo.getId());
currentOrderInfoVo.setIsHasCurrentOrder(true);
} else {
currentOrderInfoVo.setIsHasCurrentOrder(false);
}
return currentOrderInfoVo;
}

远程调用

service-order-client
OrderInfoFeignClient

1
2
3
4
5
6
7
/**
* 司机端查找当前订单
* @param driverId
* @return
*/
@GetMapping("/order/info/searchDriverCurrentOrder/{driverId}")
Result<CurrentOrderInfoVo> searchDriverCurrentOrder(@PathVariable("driverId") Long driverId);

司机端web接口

web-driver
OrderController

1
2
3
4
5
6
7
@Operation(summary = "司机端查找当前订单")  
@LoginDetection
@GetMapping("/searchDriverCurrentOrder")
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.searchDriverCurrentOrder(driverId));
}

service

1
2
3
4
5
// 司机端查找当前订单  
@Override
public CurrentOrderInfoVo searchDriverCurrentOrder(Long driverId) {
return orderInfoFeignClient.searchDriverCurrentOrder(driverId).getData();
}

获取订单信息

订单微服务接口

service-order
OrderInfoController

1
2
3
4
5
@Operation(summary = "根据订单id获取订单信息")
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfo> getOrderInfo(@PathVariable Long orderId) {
return Result.ok(orderInfoService.getById(orderId));
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**
* 根据订单id获取订单信息
* @param orderId
* @return
*/
@GetMapping("/order/info/getOrderInfo/{orderId}")
Result<OrderInfo> getOrderInfo(@PathVariable("orderId") Long orderId);

乘客端web接口

OrderController

1
2
3
4
5
6
7
@Operation(summary = "获取订单信息")  
@LoginDetection
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {
Long customerId = AuthContextHolder.getUserId();
return Result.ok(orderService.getOrderInfo(orderId, customerId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取订单信息  
@Override
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
//判断
if(orderInfo.getCustomerId() != customerId) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

OrderInfoVo orderInfoVo = new OrderInfoVo();
orderInfoVo.setOrderId(orderId);
BeanUtils.copyProperties(orderInfo,orderInfoVo);
return orderInfoVo;
}

司机端web接口

OrderController

1
2
3
4
5
6
7
@Operation(summary = "获取订单账单详细信息")  
@LoginDetection
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.getOrderInfo(orderId, driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
@Override
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
if(orderInfo.getDriverId() != driverId) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}
OrderInfoVo orderInfoVo = new OrderInfoVo();
orderInfoVo.setOrderId(orderId);
BeanUtils.copyProperties(orderInfo,orderInfoVo);
return orderInfoVo;
}

司乘同显

司机端同显

  • 司机所在地址就是司乘同显开始位置,订单地址就是司乘同显的终点
  • 计算司机司乘同显最佳路线

司机端web

OrderController

1
2
3
4
5
6
@Operation(summary = "计算最佳驾驶线路")  
@LoginDetection
@PostMapping("/calculateDrivingLine")
public Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm calculateDrivingLineForm) {
return Result.ok(orderService.calculateDrivingLine(calculateDrivingLineForm));
}

service

1
2
3
4
5
6
7
8
9
@Autowired  
private MapFeignClient mapFeignClient;

// 计算最佳驾驶线路
@Override
public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {
Result<DrivingLineVo> drivingLineVoResult = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm);
return drivingLineVoResult.getData();
}

更新位置到redis

  • 实时更新司机位置到redis

地图微服务接口service-map

LocationController

1
2
3
4
5
@Operation(summary = "司机赶往代驾起始点:更新订单地址到缓存")  
@PostMapping("/updateOrderLocationToCache")
public Result<Boolean> updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm) {
return Result.ok(locationService.updateOrderLocationToCache(updateOrderLocationForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
// 司机赶往代驾起始点:更新订单地址到缓存  
@Override
public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {

OrderLocationVo orderLocationVo = new OrderLocationVo();
orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());

String key = RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId();
redisTemplate.opsForValue().set(key, orderLocationVo);
return true;
}

远程调用service-map-client

LocationFeignClient

1
2
3
4
5
6
7
/**  
* 司机赶往代驾起始点:更新订单地址到缓存
* @param updateOrderLocationForm
* @return
*/
@PostMapping("/map/location/updateOrderLocationToCache")
Result<Boolean> updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm);

司机web端

LocationController

1
2
3
4
5
6
@Operation(summary = "司机赶往代驾起始点:更新订单位置到Redis缓存")  
@LoginDetection
@PostMapping("/updateOrderLocationToCache")
public Result updateOrderLocationToCache(@RequestBody UpdateOrderLocationForm updateOrderLocationForm) {
return Result.ok(locationService.updateOrderLocationToCache(updateOrderLocationForm));
}

service

1
2
3
4
5
// 司机赶往代驾起始点:更新订单位置到Redis缓存  
@Override
public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
return locationFeignClient.updateOrderLocationToCache(updateOrderLocationForm).getData();
}

获取司机基本信息

  • 乘客端进入司乘同显界面可以看到司机的基本信息.

司机微服务接口service-driver

DriverInfoController

1
2
3
4
5
@Operation(summary = "获取司机基本信息")  
@GetMapping("/getDriverInfo/{driverId}")
public Result<DriverInfoVo> getDriverInfoOrder(@PathVariable Long driverId) {
return Result.ok(driverInfoService.getDriverInfoOrder(driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//获取司机基本信息  
@Override
public DriverInfoVo getDriverInfoOrder(Long driverId) {
//司机id获取基本信息
DriverInfo driverInfo = driverInfoMapper.selectById(driverId);

//封装DriverInfoVo
DriverInfoVo driverInfoVo = new DriverInfoVo();
BeanUtils.copyProperties(driverInfo,driverInfoVo);

//计算驾龄
//获取当前年
int currentYear = new DateTime().getYear();
//获取驾驶证初次领证日期
//driver_license_issue_date
int firstYear = new DateTime(driverInfo.getDriverLicenseIssueDate()).getYear();
int driverLicenseAge = currentYear - firstYear;
driverInfoVo.setDriverLicenseAge(driverLicenseAge);

return driverInfoVo;
}

远程调用

DriverInfoFeignClient

1
2
3
4
5
6
7
/**
* 获取司机基本信息
* @param driverId
* @return
*/
@GetMapping("/driver/info/getDriverInfo/{driverId}")
Result<DriverInfoVo> getDriverInfo(@PathVariable("driverId") Long driverId);

乘客端web接口

OrderController

1
2
3
4
5
6
7
@Operation(summary = "根据订单id获取司机基本信息")  
@LoginDetection
@GetMapping("/getDriverInfo/{orderId}")
public Result<DriverInfoVo> getDriverInfo(@PathVariable Long orderId) {
Long customerId = AuthContextHolder.getUserId();
return Result.ok(orderService.getDriverInfo(orderId, customerId));
}

service

1
2
3
4
5
6
7
8
9
10
// 根据订单id获取司机基本信息  
@Override
public DriverInfoVo getDriverInfo(Long orderId, Long customerId) {
//根据订单id获取订单信息
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
if(orderInfo.getCustomerId() != customerId) {
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
return driverInfoFeignClient.getDriverInfo(orderInfo.getDriverId()).getData();
}

乘客端获取司机经纬度位置

  • 乘客查看司机位置

地图微服务接口service-map

LocationController

1
2
3
4
5
@Operation(summary = "司机赶往代驾起始点:获取订单经纬度位置")  
@GetMapping("/getCacheOrderLocation/{orderId}")
public Result<OrderLocationVo> getCacheOrderLocation(@PathVariable Long orderId) {
return Result.ok(locationService.getCacheOrderLocation(orderId));
}

service

1
2
3
4
5
6
7
// 司机赶往代驾起始点:获取订单经纬度位置  
@Override
public OrderLocationVo getCacheOrderLocation(Long orderId) {
String key = RedisConstant.UPDATE_ORDER_LOCATION + orderId;
OrderLocationVo orderLocationVo = (OrderLocationVo)redisTemplate.opsForValue().get(key);
return orderLocationVo;
}

远程调用

LocationFeignClient

1
2
3
4
5
6
7
/**  
* 司机赶往代驾起始点:获取订单经纬度位置
* @param orderId
* @return
*/
@GetMapping("/map/location/getCacheOrderLocation/{orderId}")
Result<OrderLocationVo> getCacheOrderLocation(@PathVariable("orderId") Long orderId);

乘客端web接口

OrderController

1
2
3
4
5
@Operation(summary = "司机赶往代驾起始点:获取订单经纬度位置")  
@GetMapping("/getCacheOrderLocation/{orderId}")
public Result<OrderLocationVo> getCacheOrderLocation(@PathVariable Long orderId) {
return Result.ok(orderService.getCacheOrderLocation(orderId));
}

service

1
2
3
4
5
// 司机赶往代驾起始点:获取订单经纬度位置  
@Override
public OrderLocationVo getCacheOrderLocation(Long orderId) {
return locationFeignClient.getCacheOrderLocation(orderId).getData();
}

乘客端同显

乘客端web接口

OrderController

1
2
3
4
5
6
@Operation(summary = "计算最佳驾驶线路")  
@LoginDetection
@PostMapping("/calculateDrivingLine")
public Result<DrivingLineVo> calculateDrivingLine(@RequestBody CalculateDrivingLineForm calculateDrivingLineForm) {
return Result.ok(orderService.calculateDrivingLine(calculateDrivingLineForm));
}

service

1
2
3
4
5
// 计算最佳驾驶线路  
@Override
public DrivingLineVo calculateDrivingLine(CalculateDrivingLineForm calculateDrivingLineForm) {
return mapFeignClient.calculateDrivingLine(calculateDrivingLineForm).getData();
}

司机到达起始点

  • 司机到达起始点后更新订单数据
  • 更新订单状态:司机已到达
  • 更新订单到达时间

订单微服务接口service-order

OrderInfoController

1
2
3
4
5
@Operation(summary = "司机到达起始点")  
@GetMapping("/driverArriveStartLocation/{orderId}/{driverId}")
public Result<Boolean> driverArriveStartLocation(@PathVariable Long orderId, @PathVariable Long driverId) {
return Result.ok(orderInfoService.driverArriveStartLocation(orderId, driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//司机到达起始点  
@Override
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {
// 更新订单状态和到达时间,条件:orderId + driverId
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
wrapper.eq(OrderInfo::getDriverId,driverId);

OrderInfo orderInfo = new OrderInfo();
orderInfo.setStatus(OrderStatus.DRIVER_ARRIVED.getStatus());
orderInfo.setArriveTime(new Date());

int rows = orderInfoMapper.update(orderInfo, wrapper);

if(rows == 1) {
return true;
} else {
throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
}
}

远程调用service-order-client

OrderInfoFeignClient

1
2
3
4
5
6
7
8
/**
* 司机到达起始点
* @param orderId
* @param driverId
* @return
*/
@GetMapping("/order/info/driverArriveStartLocation/{orderId}/{driverId}")
Result<Boolean> driverArriveStartLocation(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);

司机web调用

OrderController

1
2
3
4
5
6
7
@Operation(summary = "司机到达代驾起始地点")  
@LoginDetection
@GetMapping("/driverArriveStartLocation/{orderId}")
public Result<Boolean> driverArriveStartLocation(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.driverArriveStartLocation(orderId, driverId));
}
1
2
3
4
5
// 司机到达代驾起始地点  
@Override
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {
return orderInfoFeignClient.driverArriveStartLocation(orderId, driverId).getData();
}

司机更新代驾车辆信息

订单微服务接口service-order

OrderInfoController

1
2
3
4
5
@Operation(summary = "更新代驾车辆信息")  
@PostMapping("/updateOrderCart")
public Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm) {
return Result.ok(orderInfoService.updateOrderCart(updateOrderCartForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 更新代驾车辆信息  
@Override
public Boolean updateOrderCart(UpdateOrderCartForm updateOrderCartForm) {
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,updateOrderCartForm.getOrderId());
wrapper.eq(OrderInfo::getDriverId,updateOrderCartForm.getDriverId());

OrderInfo orderInfo = new OrderInfo();
BeanUtils.copyProperties(updateOrderCartForm,orderInfo);
orderInfo.setStatus(OrderStatus.UPDATE_CART_INFO.getStatus());

int rows = orderInfoMapper.update(orderInfo, wrapper);
if(rows == 1) {
return true;
} else {
throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
}
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**  
* 更新代驾车辆信息
* @param updateOrderCartForm
* @return
*/
@PostMapping("/order/info//updateOrderCart")
Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm);

司机web端接口

OrderController

1
2
3
4
5
6
7
8
@Operation(summary = "更新代驾车辆信息")  
@LoginDetection
@PostMapping("/updateOrderCart")
public Result<Boolean> updateOrderCart(@RequestBody UpdateOrderCartForm updateOrderCartForm) {
Long driverId = AuthContextHolder.getUserId();
updateOrderCartForm.setDriverId(driverId);
return Result.ok(orderService.updateOrderCart(updateOrderCartForm));
}

service

1
2
3
4
5
// 更新代驾车辆信息  
@Override
public Boolean updateOrderCart(UpdateOrderCartForm updateOrderCartForm) {
return orderInfoFeignClient.updateOrderCart(updateOrderCartForm).getData();
}

测试

  • 启动项目:

    • XxlJobAdminApplication
    • ServerGatewayApplication :8600/
    • ServiceCustomerApplication :8501/
    • ServiceDispatchApplication :8509/
    • ServiceDriverApplication :8502/
    • ServiceMapApplication :8503/
    • ServiceOrderApplication:8505/
    • ServiceRulesApplication :8504/
    • WebCustomerApplication :8601/
    • WebDriverApplication :8602/
  • 清除之前的数据

  • 启动微信开发者工具

修改前面的代码web-driver

FileController

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired  
private CosService cosService;

//文件上传接口
@Operation(summary = "上传")
//@LoginDetection
@PostMapping("/upload")
public Result<String> upload(@RequestPart("file") MultipartFile file,
@RequestParam(name = "path",defaultValue = "auth") String path) {
CosUploadVo cosUploadVo = cosService.uploadFile(file,path);
String showUrl = cosUploadVo.getShowUrl();
return Result.ok(showUrl);
}

订单执行(二)

开始服务

  • 更新订单状态

订单微服务接口

OrderInfoController

1
2
3
4
5
6
@Operation(summary = "开始服务")  
@PostMapping("/startDrive")
public Result<Boolean> startDriver(@RequestBody StartDriveForm startDriveForm) {
Boolean flag = orderInfoService.startDriver(startDriveForm);
return Result.ok(flag);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//开始代驾服务  
@Override
public Boolean startDriver(StartDriveForm startDriveForm) {
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,startDriveForm.getOrderId());
wrapper.eq(OrderInfo::getDriverId,startDriveForm.getDriverId());

OrderInfo orderInfo = new OrderInfo();
orderInfo.setStatus(OrderStatus.START_SERVICE.getStatus());
orderInfo.setStartServiceTime(new Date());

int rows = orderInfoMapper.update(orderInfo, wrapper);
if(rows == 1) {
return true;
} else {
throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
}
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**  
* 开始代驾服务
* @param startDriveForm
* @return
*/
@PostMapping("/order/info/startDrive")
Result<Boolean> startDrive(@RequestBody StartDriveForm startDriveForm);

司机web端调用

OrderController

1
2
3
4
5
6
7
8
@Operation(summary = "开始代驾服务")  
@LoginDetection
@PostMapping("/startDrive")
public Result<Boolean> startDrive(@RequestBody StartDriveForm startDriveForm) {
Long driverId = AuthContextHolder.getUserId();
startDriveForm.setDriverId(driverId);
return Result.ok(orderService.startDrive(startDriveForm));
}

service

1
2
3
4
5
// 开始代驾服务  
@Override
public Boolean startDrive(StartDriveForm startDriveForm) {
return orderInfoFeignClient.startDrive(startDriveForm).getData();
}

批量保存订单位置信息

  • 开始服务后,司机端会实时收集司机位置
  • 定时批量上传位置到后台服务保存到MongoDB

地图微服务接口

LocationController

1
2
3
4
5
@Operation(summary = "批量保存代驾服务订单位置")  
@PostMapping("/saveOrderServiceLocation")
public Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList) {
return Result.ok(locationService.saveOrderServiceLocation(orderLocationServiceFormList));
}

编写接口

1
2
3
@Repository  
public interface OrderServiceLocationRepository extends MongoRepository<OrderServiceLocation, String> {
}

导入依赖

1
2
3
4
5
<!--mongodb-->  
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired  
private OrderServiceLocationRepository orderServiceLocationRepository;

@Override
public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
List<OrderServiceLocation> list = new ArrayList<>();
// 遍历,变成OrderServiceLocation
orderLocationServiceFormList.forEach(orderServiceLocationForm->{
//orderServiceLocationForm -- OrderServiceLocation
OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
BeanUtils.copyProperties(orderServiceLocationForm,orderServiceLocation);
orderServiceLocation.setId(ObjectId.get().toString());
orderServiceLocation.setCreateTime(new Date());

list.add(orderServiceLocation);
});
//批量添加到MongoDB
orderServiceLocationRepository.saveAll(list);
return true;
}

远程调用

LocationFeignClient

1
2
3
4
5
6
7
/**  
* 开始代驾服务:保存代驾服务订单位置
* @param orderLocationServiceFormList
* @return
*/
@PostMapping("/map/location/saveOrderServiceLocation")
Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList);

司机web端调用

LocationController

1
2
3
4
5
@Operation(summary = "开始代驾服务:保存代驾服务订单位置")  
@PostMapping("/saveOrderServiceLocation")
public Result<Boolean> saveOrderServiceLocation(@RequestBody List<OrderServiceLocationForm> orderLocationServiceFormList) {
return Result.ok(locationService.saveOrderServiceLocation(orderLocationServiceFormList));
}

service

1
2
3
4
5
// 始代驾服务:保存代驾服务订单位置  
@Override
public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
return locationFeignClient.saveOrderServiceLocation(orderLocationServiceFormList).getData();
}

获取订单最后一个位置

  • 司机开始服务后,乘客端获取司机的最新动向

地图微服务接口

LocationController

1
2
3
4
5
@Operation(summary = "代驾服务:获取订单服务最后一个位置信息")  
@GetMapping("/getOrderServiceLastLocation/{orderId}")
public Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId) {
return Result.ok(locationService.getOrderServiceLastLocation(orderId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired  
private MongoTemplate mongoTemplate;

// 代驾服务:获取订单服务最后一个位置信息
@Override
public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
//查询MongoDB,查询条件 :orderId
Query query = new Query();
query.addCriteria(Criteria.where("orderId").is(orderId));
//根据创建时间降序排列
query.with(Sort.by(Sort.Order.desc("createTime")));
//只取一条数据
query.limit(1);

OrderServiceLocation orderServiceLocation =
mongoTemplate.findOne(query, OrderServiceLocation.class);
OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
BeanUtils.copyProperties(orderServiceLocation,orderServiceLastLocationVo);
return orderServiceLastLocationVo;
}

远程调用

LocationFeignClient

1
2
3
4
5
6
7
/**  
* 代驾服务:获取订单服务最后一个位置信息
* @param orderId
* @return
*/
@GetMapping("/map/location/getOrderServiceLastLocation/{orderId}")
Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId);

乘客web端调用

OrderController

1
2
3
4
5
6
@Operation(summary = "代驾服务:获取订单服务最后一个位置信息")  
@LoginDetection
@GetMapping("/getOrderServiceLastLocation/{orderId}")
public Result<OrderServiceLastLocationVo> getOrderServiceLastLocation(@PathVariable Long orderId) {
return Result.ok(orderService.getOrderServiceLastLocation(orderId));
}

service

1
2
3
4
5
// 代驾服务:获取订单服务最后一个位置信息  
@Override
public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
return locationFeignClient.getOrderServiceLastLocation(orderId).getData();
}

Minio上传接口

  • 司机服务过程中,司机端小程序实时采集录音,把录音和对话文本上传到后台服务,把完整监控保存到Minio

Minio安装

使用docker安装

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建数据存储目录
mkdir -p ~/minio/data

// 创建minio
docker run \
-p 9000:9000 \
-p 9090:9090 \
--name minio \
-v ~/minio/data:/data \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-d \
quay.io/minio/minio server /data --console-address ":9090"

2 安装到windows里面

  • 找到minio安装文件,放到没有中文没有空格目录

  • 创建空文件夹,作为数据存储目录

  • 在windows使用命令启动Minio服务

– minio.exe server D:\Desktop\hhsqdmz\sort\minio\data

Minio启动

  • 访问Minio控制台:ip:9000

  • 默认控制台用户名和密码都是:minioadmin

  • 在minio控制台里创建buckets

  • 创建buckets后记得把Access Policy设为public

司机端web接口

导入依赖

1
2
3
4
<dependency>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

修改配置文件common-account.yaml

1
2
3
4
5
minio:
endpointUrl: http://localhost:9000
accessKey: minioadmin
secreKey: minioadmin
bucketName: daijia

创建配置类,读取minio的值

1
2
3
4
5
6
7
8
9
10
@Configuration  
@ConfigurationProperties(prefix="minio") //读取节点
@Data
public class MinioProperties {

private String endpointUrl;
private String accessKey;
private String secreKey;
private String bucketName;
}

FileController

1
2
3
4
5
6
7
8
9
@Autowired  
private FileService fileService;

@Operation(summary = "上传")
@PostMapping("/upload")
public Result<String> upload(@RequestPart("file") MultipartFile file) {
String url = fileService.upload(file);
return Result.ok(url);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Autowired  
private MinioProperties minioProperties;

// 上传
@Override
public String upload(MultipartFile file) {
try {
// 创建一个Minio的客户端对象
MinioClient minioClient = MinioClient.builder()
.endpoint(minioProperties.getEndpointUrl())
.credentials(minioProperties.getAccessKey(), minioProperties.getSecreKey())
.build();

// 判断桶是否存在
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
if (!found) { // 如果不存在,那么此时就创建一个新的桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
} else { // 如果存在打印信息
System.out.println("Bucket 'daijia' already exists.");
}

// 设置存储对象名称
String extFileName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String fileName = new SimpleDateFormat("yyyyMMdd")
.format(new Date()) + "/" + UUID.randomUUID().toString().replace("-" , "") + "." + extFileName;

PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.stream(file.getInputStream(), file.getSize(), -1)
.object(fileName)
.build();
minioClient.putObject(putObjectArgs) ;

return minioProperties.getEndpointUrl() + "/" + minioProperties.getBucketName() + "/" + fileName ;

} catch (Exception e) {
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
}

保存订单监控记录数据

  • 订单执行过程中,记录对话信息
  • 在前端小程序,同声传译,把录音转换文本,保存文本内存

订单微服务接口.

导入依赖

1
2
3
4
<dependency>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

添加MongoRepository

1
2
3
@Repository  
public interface OrderMonitorRecordRepository extends MongoRepository<OrderMonitorRecord, String> {
}

OrderMonitorController

1
2
3
4
5
6
7
8
@Autowired  
private OrderMonitorService orderMonitorService;

@Operation(summary = "保存订单监控记录数据")
@PostMapping("/saveOrderMonitorRecord")
public Result<Boolean> saveMonitorRecord(@RequestBody OrderMonitorRecord orderMonitorRecord) {
return Result.ok(orderMonitorService.saveOrderMonitorRecord(orderMonitorRecord));
}

service

1
2
3
4
5
6
7
8
9
@Autowired  
private OrderMonitorRecordRepository orderMonitorRecordRepository;

// 保存订单监控记录数据
@Override
public Boolean saveOrderMonitorRecord(OrderMonitorRecord orderMonitorRecord) {
orderMonitorRecordRepository.save(orderMonitorRecord);
return true;
}

远程调用

OrderMonitorFeignClient

1
2
3
4
5
6
7
/**  
* 保存订单监控记录数据
* @param orderMonitorRecord
* @return
*/
@PostMapping("/order/monitor/saveOrderMonitorRecord")
Result<Boolean> saveMonitorRecord(@RequestBody OrderMonitorRecord orderMonitorRecord);

司机端web调用

MonitorController

1
2
3
4
5
6
7
8
9
10
@Autowired  
private MonitorService monitorService;

@Operation(summary = "上传录音")
@PostMapping("/upload")
public Result<Boolean> upload(@RequestParam("file") MultipartFile file,
OrderMonitorForm orderMonitorForm) {

return Result.ok(monitorService.upload(file, orderMonitorForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired  
private FileService fileService;

@Autowired
private OrderMonitorFeignClient orderMonitorFeignClient;

// 上传录音
@Override
public Boolean upload(MultipartFile file, OrderMonitorForm orderMonitorForm) {
//上传文件
String url = fileService.upload(file);

OrderMonitorRecord orderMonitorRecord = new OrderMonitorRecord();
orderMonitorRecord.setOrderId(orderMonitorForm.getOrderId());
orderMonitorRecord.setFileUrl(url);
orderMonitorRecord.setContent(orderMonitorForm.getContent());
orderMonitorFeignClient.saveMonitorRecord(orderMonitorRecord);

return true;
}

订单监控审核

使用腾讯云数据万象实现自动审核

腾讯云COS图片审核

封装图片审核方法

在service-driver的CiService

1
Boolean imageAuditing(String path);

CiServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Service  
public class CiServiceImpl implements CiService {

@Autowired
private TencentCloudProperties tencentCloudProperties;

//图片审核
@Override
public Boolean imageAuditing(String path) {

//1.创建任务请求对象
ImageAuditingRequest request = new ImageAuditingRequest();
//2.添加请求参数 参数详情请见 API 接口文档
//2.1设置请求 bucket request.setBucketName(tencentCloudProperties.getBucketPrivate());
//2.2设置审核策略 不传则为默认策略(预设)
//request.setBizType("");
//2.3设置 bucket 中的图片位置
request.setObjectKey(path);
//3.调用接口,获取任务响应对象
COSClient client = this.getCosClient();
ImageAuditingResponse response = client.imageAuditing(request);
client.shutdown();
//用于返回该审核场景的审核结果,返回值:0:正常。1:确认为当前场景的违规内容。2:疑似为当前场景的违规内容。
if (!response.getPornInfo().getHitFlag().equals("0")
|| !response.getAdsInfo().getHitFlag().equals("0")
|| !response.getTerroristInfo().getHitFlag().equals("0")
|| !response.getPoliticsInfo().getHitFlag().equals("0")
) {
return false;
}
return true;
}

public COSClient getCosClient() {
String secretId = tencentCloudProperties.getSecretId();
String secretKey = tencentCloudProperties.getSecretKey();
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置 bucket 的地域, COS 地域
Region region = new Region(tencentCloudProperties.getRegion());
ClientConfig clientConfig = new ClientConfig(region);
// 这里建议设置使用 https 协议
clientConfig.setHttpProtocol(HttpProtocol.https);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);
return cosClient;
}
}

腾讯云COS图片添加审核

CosServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Autowired  
private CiService ciService;

@Override
public CosUploadVo upload(MultipartFile file, String path) {
//获取cosClient对象
COSClient cosClient = this.getCosClient();
//文件上传
//元数据信息
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(file.getSize());
meta.setContentEncoding("UTF-8");
meta.setContentType(file.getContentType());

//向存储桶中保存文件
String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); //文件后缀名
String uploadPath = "/driver/" + path + "/" + UUID.randomUUID().toString().replaceAll("-", "") + fileType;
// 01.jpg
// /driver/auth/0o98754.jpg PutObjectRequest putObjectRequest = null;
try {
//1 bucket名称
//2
putObjectRequest = new PutObjectRequest(tencentCloudProperties.getBucketPrivate(),
uploadPath,
file.getInputStream(),
meta);
} catch (IOException e) {
throw new RuntimeException(e);
}
putObjectRequest.setStorageClass(StorageClass.Standard);
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); //上传文件
cosClient.shutdown();

// 图片审核
Boolean imageAuditing = ciService.imageAuditing(uploadPath);
if(!imageAuditing){
cosClient.deleteObject(tencentCloudProperties.getBucketPrivate(), uploadPath);
throw new GuiguException(ResultCodeEnum.IMAGE_AUDITION_FAIL);
}

//返回vo对象
CosUploadVo cosUploadVo = new CosUploadVo();
cosUploadVo.setUrl(uploadPath);
//图片临时访问url,回显使用
String imageUrl = this.getImageUrl(uploadPath);
cosUploadVo.setShowUrl(imageUrl);
return cosUploadVo;
}

封装文本审核接口

司机微服务接口

CiController

1
2
3
4
5
6
7
8
@Autowired  
private CiService ciService;

@Operation(summary = "文本审核")
@PostMapping("/textAuditing")
public Result<TextAuditingVo> textAuditing(@RequestBody String content) {
return Result.ok(ciService.textAuditing(content));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 文本审核  
@Override
public TextAuditingVo textAuditing(String content) {
if(!StringUtils.hasText(content)) {
TextAuditingVo textAuditingVo = new TextAuditingVo();
textAuditingVo.setResult("0");
return textAuditingVo;
}

COSClient cosClient = this.getCosClient();

//1.创建任务请求对象
TextAuditingRequest request = new TextAuditingRequest();
//2.添加请求参数 参数详情请见 API 接口文档
request.setBucketName(tencentCloudProperties.getBucketPrivate());
//2.1.1设置请求内容,文本内容的Base64编码
byte[] encoder = org.apache.commons.codec.binary.Base64.encodeBase64(content.getBytes());
String contentBase64 = new String(encoder);
request.getInput().setContent(contentBase64);
request.getConf().setDetectType("all");

//3.调用接口,获取任务响应对象
TextAuditingResponse response = cosClient.createAuditingTextJobs(request);
AuditingJobsDetail detail = response.getJobsDetail();
TextAuditingVo textAuditingVo = new TextAuditingVo();
if ("Success".equals(detail.getState())) {
//检测结果: 0(审核正常),1 (判定为违规敏感文件),2(疑似敏感,建议人工复核)。
String result = detail.getResult();

//违规关键词
StringBuffer keywords = new StringBuffer();
List<SectionInfo> sectionInfoList = detail.getSectionList();
for (SectionInfo info : sectionInfoList) {

String pornInfoKeyword = info.getPornInfo().getKeywords();
String illegalInfoKeyword = info.getIllegalInfo().getKeywords();
String abuseInfoKeyword = info.getAbuseInfo().getKeywords();

if (pornInfoKeyword.length() > 0) {
keywords.append(pornInfoKeyword).append(",");
}
if (illegalInfoKeyword.length() > 0) {
keywords.append(illegalInfoKeyword).append(",");
}
if (abuseInfoKeyword.length() > 0) {
keywords.append(abuseInfoKeyword).append(",");
}
}
textAuditingVo.setResult(result);
textAuditingVo.setKeywords(keywords.toString());
}
return textAuditingVo;
}

远程调用

CiFeignClient

1
2
3
4
5
6
7
/**  
* 文本审核
* @param content
* @return
*/
@PostMapping("/ci/textAuditing")
Result<TextAuditingVo> textAuditing(@RequestBody String content);

订单监控接口完善

修改web-driver的MonitorServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Autowired  
private FileService fileService;

@Autowired
private OrderMonitorFeignClient orderMonitorFeignClient;

@Autowired
private CiFeignClient ciFeignClient;

// 上传录音
@Override
public Boolean upload(MultipartFile file, OrderMonitorForm orderMonitorForm) {
//上传文件
String url = fileService.upload(file);

OrderMonitorRecord orderMonitorRecord = new OrderMonitorRecord();
orderMonitorRecord.setOrderId(orderMonitorForm.getOrderId());
orderMonitorRecord.setFileUrl(url);
orderMonitorRecord.setContent(orderMonitorForm.getContent());

// 文本审核
TextAuditingVo textAuditingVo = ciFeignClient.textAuditing(orderMonitorForm.getContent()).getData();
orderMonitorRecord.setResult(textAuditingVo.getResult());
orderMonitorRecord.setKeywords(textAuditingVo.getKeywords());

orderMonitorFeignClient.saveMonitorRecord(orderMonitorRecord);

return true;
}

订单执行(三)

计算订单实际里程

  • 在MongoDB保存订单过程中司机位置信息,把MongoDB存储司机位置信息获取出来,以时间排序,连接每个点,成为实际距离

准备计算距离工具类

common-util中的LocationUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static double getDistance(double lat1, double lng1, double lat2,  
double lng2) {
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a = radLat1 - radLat2;
double b = rad(lng1) - rad(lng2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2)
* Math.pow(Math.sin(b / 2), 2)));
s = s * EARTH_RADIUS;
s = Math.round(s * 10000d) / 10000d;
s = s * 1000;
return s;
}

地图微服务接口

LocationController

1
2
3
4
5
@Operation(summary = "代驾服务:计算订单实际里程")  
@GetMapping("/calculateOrderRealDistance/{orderId}")
public Result<BigDecimal> calculateOrderRealDistance(@PathVariable Long orderId) {
return Result.ok(locationService.calculateOrderRealDistance(orderId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 代驾服务:计算订单实际里程  
@Override
public BigDecimal calculateOrderRealDistance(Long orderId) {
// 根据订单id获取位置信息,按照创建时间排序
List<OrderServiceLocation> list =
orderServiceLocationRepository.findByOrderIdOrderByCreateTimeAsc(orderId);

// 第一步查询返回订单位置信息list集合
// 把list集合遍历,得到每个位置信息,计算两个位置距离,把计算所有距离相加操作
double realDistance = 0;
if(!CollectionUtils.isEmpty(list)) {
for (int i = 0,size = list.size()-1; i < size; i++) {
OrderServiceLocation location1 = list.get(i);
OrderServiceLocation location2 = list.get(i + 1);

//计算位置距离
double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(),
location1.getLongitude().doubleValue(),
location2.getLatitude().doubleValue(),
location2.getLongitude().doubleValue());

realDistance += distance;
}
}

return new BigDecimal(realDistance);
}

远程调用

LocationFeignClient

1
2
3
4
5
6
7
/**
* 代驾服务:计算订单实际里程
* @param orderId
* @return
*/
@GetMapping("/map/location/calculateOrderRealDistance/{orderId}")
Result<BigDecimal> calculateOrderRealDistance(@PathVariable Long orderId);

计算系统奖励

创建规则文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问  
package com.atguigu.daijia

import com.atguigu.daijia.model.form.rules.RewardRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

global com.atguigu.daijia.model.vo.rules.RewardRuleResponse rewardRuleResponse;

/**
系统奖励
00:00:00-06:59:59 完成5单后 奖励5元
07:00:00-23:59:59 完成10单后 奖励2元
*/
rule "00:00:00-06:59:59 完成5单后 每单奖励5元"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:RewardRuleRequest(startTime >= "00:00:00" && startTime <= "06:59:59" && orderNum > 5)
then
rewardRuleResponse.setRewardAmount(new BigDecimal("5.0"));
System.out.println("00:00:00-06:59:59 奖励:" + rewardRuleResponse.getRewardAmount() + "元");
end
rule "07:00:00-23:59:59 完成10单后 每单奖励2元"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:RewardRuleRequest(startTime >= "07:00:00" && startTime <= "23:59:59" && orderNum > 10)
then
rewardRuleResponse.setRewardAmount(new BigDecimal("2.0"));
System.out.println("00:00:00-06:59:59 奖励:" + rewardRuleResponse.getRewardAmount() + "元");
end

添加规则引擎工具类

在service-rules中utils包下创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DroolsHelper {  

private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";

public static KieSession loadForRule(String drlStr) {
KieServices kieServices = KieServices.Factory.get();

KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(
ResourceFactory.newClassPathResource(drlStr));

KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();

KieModule kieModule = kb.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kieContainer.newKieSession();
}
}

规则微服务接口

RewardRuleController

1
2
3
4
5
6
7
8
9
@Autowired  
private RewardRuleService rewardRuleService;

@Operation(summary = "计算订单奖励费用")
@PostMapping("/calculateOrderRewardFee")
public Result<RewardRuleResponseVo>
calculateOrderRewardFee(@RequestBody RewardRuleRequestForm rewardRuleRequestForm) {
return Result.ok(rewardRuleService.calculateOrderRewardFee(rewardRuleRequestForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 计算订单奖励费用  
@Override
public RewardRuleResponseVo calculateOrderRewardFee(RewardRuleRequestForm rewardRuleRequestForm) {
//封装传入参数对象
RewardRuleRequest rewardRuleRequest = new RewardRuleRequest();
rewardRuleRequest.setOrderNum(rewardRuleRequestForm.getOrderNum());

//创建规则引擎对象
KieSession kieSession = DroolsHelper.loadForRule(RULES_CUSTOMER_RULES_DRL);

//封装返回对象
RewardRuleResponse rewardRuleResponse = new RewardRuleResponse();
kieSession.setGlobal("rewardRuleResponse",rewardRuleResponse);

//设置对象,触发规则
kieSession.insert(rewardRuleRequest);
kieSession.fireAllRules();

//终止会话
kieSession.dispose();

//封装RewardRuleResponseVo
RewardRuleResponseVo rewardRuleResponseVo = new RewardRuleResponseVo();
rewardRuleResponseVo.setRewardAmount(rewardRuleResponse.getRewardAmount());
return rewardRuleResponseVo;
}

远程调用

RewardRuleFeignClient

1
2
3
4
5
6
7
/**
* 计算订单奖励费用
* @param rewardRuleRequestForm
* @return
*/
@PostMapping("/rules/reward/calculateOrderRewardFee")
Result<RewardRuleResponseVo> calculateOrderRewardFee(@RequestBody RewardRuleRequestForm rewardRuleRequestForm);

根据时间段获取订单数量

订单微服务接口

OrderInfoController

1
2
3
4
5
@Operation(summary = "根据时间段获取订单数")  
@GetMapping("/getOrderNumByTime/{startTime}/{endTime}")
public Result<Long> getOrderNumByTime(@PathVariable String startTime, @PathVariable String endTime) {
return Result.ok(orderInfoService.getOrderNumByTime(startTime, endTime));
}

service

1
2
3
4
5
6
7
8
9
// 根据时间段获取订单数  
@Override
public Long getOrderNumByTime(String startTime, String endTime) {
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(OrderInfo::getStartServiceTime,startTime);
wrapper.lt(OrderInfo::getStartServiceTime,endTime);
Long count = orderInfoMapper.selectCount(wrapper);
return count;
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
8
/**  
* 根据时间段获取订单数
* @param startTime
* @param endTime
* @return
*/
@GetMapping("/order/info/getOrderNumByTime/{startTime}/{endTime}")
Result<Long> getOrderNumByTime(@PathVariable("startTime") String startTime, @PathVariable("endTime") String endTime);

计算分账信息

创建规则文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问  
package com.atguigu.daijia

import com.atguigu.daijia.model.form.rules.ProfitsharingRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

global com.atguigu.daijia.model.vo.rules.ProfitsharingRuleResponse profitsharingRuleResponse;
//支付微信平台费率:0.6%
//global BigDecimal paymentRate = new BigDecimal(0.006);
/**
支付微信平台费用
平台费率:0.6%
*/
rule "支付微信平台费用 平台费率:0.6%"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:ProfitsharingRuleRequest()
then
profitsharingRuleResponse.setOrderAmount($rule.getOrderAmount());
profitsharingRuleResponse.setPaymentRate(new BigDecimal("0.006"));
BigDecimal paymentFee = profitsharingRuleResponse.getOrderAmount().multiply(profitsharingRuleResponse.getPaymentRate()).setScale(2, RoundingMode.HALF_UP);
profitsharingRuleResponse.setPaymentFee(paymentFee);
System.out.println("支付微信平台费用:" + profitsharingRuleResponse.getPaymentFee() + "元");
end

/**
订单金额小于等于100
当天完成订单小于等于10单 平台抽成 20%
当天完成订单大于10单 平台抽成 18%
*/
rule "订单金额小于等于100 当天完成订单小于等于10单"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:ProfitsharingRuleRequest(orderAmount.doubleValue() <= 100.0 && orderNum <= 10)
then
BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());
BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.2")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);
//代驾司机个税,税率:10%
BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);
profitsharingRuleResponse.setPlatformIncome(platformIncome);
profitsharingRuleResponse.setDriverIncome(driverIncome);
profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));
profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);
System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");
end
rule "订单金额小于等于100 天完成订单大于10单"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:ProfitsharingRuleRequest(orderAmount.doubleValue() <= 100.0 && orderNum > 10)
then
BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());
BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);
//代驾司机个税,税率:10%
BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);
profitsharingRuleResponse.setPlatformIncome(platformIncome);
profitsharingRuleResponse.setDriverIncome(driverIncome);
profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));
profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);
System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");
end

/**
订单金额大于100
当天完成订单小于等于10单 平台抽成 18%
当天完成订单大于10单 平台抽成 16%
*/
rule "订单金额大于100 当天完成订单小于等于10单"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:ProfitsharingRuleRequest(orderAmount.doubleValue() > 100.0 && orderNum <= 10)
then
BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());
BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);
//代驾司机个税,税率:10%
BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);
profitsharingRuleResponse.setPlatformIncome(platformIncome);
profitsharingRuleResponse.setDriverIncome(driverIncome);
profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));
profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);
System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");
end
rule "订单金额大于100 天完成订单大于10单"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:ProfitsharingRuleRequest(orderAmount.doubleValue() > 100.0 && orderNum > 10)
then
BigDecimal totalAmount = profitsharingRuleResponse.getOrderAmount().subtract(profitsharingRuleResponse.getPaymentFee());
BigDecimal platformIncome = totalAmount.multiply(new BigDecimal("0.18")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverTotalIncome = totalAmount.subtract(platformIncome);
//代驾司机个税,税率:10%
BigDecimal driverTaxFee = driverTotalIncome.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.HALF_UP);
BigDecimal driverIncome = driverTotalIncome.subtract(driverTaxFee);
profitsharingRuleResponse.setPlatformIncome(platformIncome);
profitsharingRuleResponse.setDriverIncome(driverIncome);
profitsharingRuleResponse.setDriverTaxRate(new BigDecimal("0.1"));
profitsharingRuleResponse.setDriverTaxFee(driverTaxFee);
System.out.println("平台分账收入:" + platformIncome + "元" + ",司机分账收入:" + driverIncome + "元" + ",司机个税:" + driverTaxFee + "元");
end

规则微服务接口

ProfitsharingRuleController

1
2
3
4
5
6
7
8
@Autowired  
private ProfitsharingRuleService profitsharingRuleService;

@Operation(summary = "计算系统分账费用")
@PostMapping("/calculateOrderProfitsharingFee")
public Result<ProfitsharingRuleResponseVo> calculateOrderProfitsharingFee(@RequestBody ProfitsharingRuleRequestForm profitsharingRuleRequestForm) {
return Result.ok(profitsharingRuleService.calculateOrderProfitsharingFee(profitsharingRuleRequestForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Autowired  
private ProfitsharingRuleMapper rewardRuleMapper;

private static final String RULES_CUSTOMER_RULES_DRL = "rules/ProfitsharingRule.drl";

@Override
public ProfitsharingRuleResponseVo calculateOrderProfitsharingFee(ProfitsharingRuleRequestForm profitsharingRuleRequestForm) {
//传入参数对象封装
ProfitsharingRuleRequest profitsharingRuleRequest = new ProfitsharingRuleRequest();
profitsharingRuleRequest.setOrderAmount(profitsharingRuleRequestForm.getOrderAmount());
profitsharingRuleRequest.setOrderNum(profitsharingRuleRequestForm.getOrderNum());

//创建kieSession
KieSession kieSession = DroolsHelper.loadForRule(RULES_CUSTOMER_RULES_DRL);

//封装返回对象
ProfitsharingRuleResponse profitsharingRuleResponse = new ProfitsharingRuleResponse();
kieSession.setGlobal("profitsharingRuleResponse",profitsharingRuleResponse);

//触发规则,返回vo对象
kieSession.insert(profitsharingRuleRequest);
kieSession.fireAllRules();
kieSession.dispose();

ProfitsharingRuleResponseVo profitsharingRuleResponseVo = new ProfitsharingRuleResponseVo();
BeanUtils.copyProperties(profitsharingRuleResponse,profitsharingRuleResponseVo);

return profitsharingRuleResponseVo;
}

远程调用

ProfitsharingRuleFeignClient

1
2
3
4
5
6
7
/**  
* 计算订单分账数据
* @param profitsharingRuleRequestForm
* @return
*/
@PostMapping("/rules/profitsharing/calculateOrderProfitsharingFee")
Result<ProfitsharingRuleResponseVo> calculateOrderProfitsharingFee(@RequestBody ProfitsharingRuleRequestForm profitsharingRuleRequestForm);

结束服务更新订单

  • 更新订单数据:订单状态、订单实际距离、订单实际金额等
  • 添加实际账单信息
  • 添加分账信息

订单微服务接口

OrderInfoController

1
2
3
4
5
@Operation(summary = "结束代驾服务更新订单账单")  
@PostMapping("/endDrive")
public Result<Boolean> endDrive(@RequestBody UpdateOrderBillForm updateOrderBillForm) {
return Result.ok(orderInfoService.endDrive(updateOrderBillForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Autowired  
private OrderBillMapper orderBillMapper;

@Autowired
private OrderProfitsharingMapper orderProfitsharingMapper;

// 结束代驾服务更新订单账单
@Override
public Boolean endDrive(UpdateOrderBillForm updateOrderBillForm) {
// 更新订单信息
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,updateOrderBillForm.getOrderId());
wrapper.eq(OrderInfo::getDriverId,updateOrderBillForm.getDriverId());

OrderInfo orderInfo = new OrderInfo();
orderInfo.setStatus(OrderStatus.END_SERVICE.getStatus());
orderInfo.setRealAmount(updateOrderBillForm.getTotalAmount());
orderInfo.setFavourFee(updateOrderBillForm.getFavourFee());
orderInfo.setRealDistance(updateOrderBillForm.getRealDistance());
orderInfo.setEndServiceTime(new Date());

int rows = orderInfoMapper.update(orderInfo, wrapper);

if(rows == 1) {
//添加账单数据
OrderBill orderBill = new OrderBill();
BeanUtils.copyProperties(updateOrderBillForm,orderBill);
orderBill.setOrderId(updateOrderBillForm.getOrderId());
orderBill.setPayAmount(updateOrderBillForm.getTotalAmount());
orderBillMapper.insert(orderBill);

//添加分账信息
OrderProfitsharing orderProfitsharing = new OrderProfitsharing();
BeanUtils.copyProperties(updateOrderBillForm, orderProfitsharing);
orderProfitsharing.setOrderId(updateOrderBillForm.getOrderId());
orderProfitsharing.setRuleId(updateOrderBillForm.getProfitsharingRuleId());
orderProfitsharing.setStatus(1);
orderProfitsharingMapper.insert(orderProfitsharing);

} else {
throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
}
return true;
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**  
* 结束代驾服务更新订单账单
* @param updateOrderBillForm
* @return
*/
@PostMapping("/order/info/endDrive")
Result<Boolean> endDrive(@RequestBody UpdateOrderBillForm updateOrderBillForm);

结束服务

web-driver中的OrderController

1
2
3
4
5
6
7
8
@Operation(summary = "结束代驾服务更新订单账单")  
@LoginDetection
@PostMapping("/endDrive")
public Result<Boolean> endDrive(@RequestBody OrderFeeForm orderFeeForm) {
Long driverId = AuthContextHolder.getUserId();
orderFeeForm.setDriverId(driverId);
return Result.ok(orderService.endDrive(orderFeeForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@Autowired  
private LocationFeignClient locationFeignClient;

@Autowired
private FeeRuleFeignClient feeRuleFeignClient;

@Autowired
private RewardRuleFeignClient rewardRuleFeignClient;

@Autowired
private ProfitsharingRuleFeignClient profitsharingRuleFeignClient;

// 结束代驾服务更新订单账单
@Override
public Boolean endDrive(OrderFeeForm orderFeeForm) {
// 根据orderId获取订单信息,判断当前订单是否司机接单
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();
if(orderInfo.getDriverId() != orderFeeForm.getDriverId()) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

// 计算订单实际里程
BigDecimal realDistance =
locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();

// 计算代驾实际费用
//封装FeeRuleRequestForm
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDistance(realDistance);
feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());

//计算司机到达代驾开始位置时间
Integer waitMinute =
Math.abs((int)((orderInfo.getStartServiceTime().getTime()-orderInfo.getArriveTime().getTime())/(1000 * 60)));
feeRuleRequestForm.setWaitMinute(waitMinute);
//远程调用 代驾费用
FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();
//实际费用 = 代驾费用 + 其他费用(停车费)
BigDecimal totalAmount =
feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())
.add(orderFeeForm.getParkingFee())
.add(orderFeeForm.getOtherFee())
.add(orderInfo.getFavourFee());
feeRuleResponseVo.setTotalAmount(totalAmount);

// 计算系统奖励
String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";
String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";
Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();

RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();
rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());
rewardRuleRequestForm.setOrderNum(orderNum);

RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();

// 计算分账信息
ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();
profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());
profitsharingRuleRequestForm.setOrderNum(orderNum);

ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();

// 封装实体类,结束代驾更新订单,添加账单和分账信息
UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();
updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());
updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());
//路桥费、停车费、其他费用
updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());
updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());
updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());
//乘客好处费
updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());

//实际里程
updateOrderBillForm.setRealDistance(realDistance);
//订单奖励信息
BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);
//代驾费用信息
BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);
//分账相关信息
BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);
updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());
orderInfoFeignClient.endDrive(updateOrderBillForm);

return true;
}

添加位置限定

修改之前web-driver中OrderServiceImpl编写的方法,在开始订单做判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//司机到达代驾起始地点  
@Override
public Boolean driverArriveStartLocation(Long orderId, Long driverId) {
//判断
// orderInfo有代驾开始位置
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();

//司机当前位置
OrderLocationVo orderLocationVo = locationFeignClient.getCacheOrderLocation(orderId).getData();

//司机当前位置 和 代驾开始位置距离
double distance = LocationUtil.getDistance(orderInfo.getStartPointLatitude().doubleValue(),
orderInfo.getStartPointLongitude().doubleValue(),
orderLocationVo.getLatitude().doubleValue(),
orderLocationVo.getLongitude().doubleValue());
if(distance > SystemConstant.DRIVER_START_LOCATION_DISTION) {
throw new GuiguException(ResultCodeEnum.DRIVER_START_LOCATION_DISTION_ERROR);
}

return orderInfoFeignClient.driverArriveStartLocation(orderId,driverId).getData();
}

结束订单也是一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 结束代驾服务更新订单账单  
@Override
public Boolean endDrive(OrderFeeForm orderFeeForm) {
// 根据orderId获取订单信息,判断当前订单是否司机接单
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();
if(orderInfo.getDriverId() != orderFeeForm.getDriverId()) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

// 判断距离
OrderServiceLastLocationVo orderServiceLastLocationVo = locationFeignClient.getOrderServiceLastLocation(orderFeeForm.getOrderId()).getData();

//司机当前位置 距离 结束代驾位置
double distance = LocationUtil.getDistance(orderInfo.getEndPointLatitude().doubleValue(),
orderInfo.getEndPointLongitude().doubleValue(),
orderServiceLastLocationVo.getLatitude().doubleValue(),
orderServiceLastLocationVo.getLongitude().doubleValue());
if(distance > SystemConstant.DRIVER_END_LOCATION_DISTION) {
throw new GuiguException(ResultCodeEnum.DRIVER_END_LOCATION_DISTION_ERROR);
}

// 计算订单实际里程
BigDecimal realDistance =
locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();

// 计算代驾实际费用
//封装FeeRuleRequestForm
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDistance(realDistance);
feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());

//计算司机到达代驾开始位置时间
Integer waitMinute =
Math.abs((int)((orderInfo.getStartServiceTime().getTime()-orderInfo.getArriveTime().getTime())/(1000 * 60)));
feeRuleRequestForm.setWaitMinute(waitMinute);
//远程调用 代驾费用
FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();
//实际费用 = 代驾费用 + 其他费用(停车费)
BigDecimal totalAmount =
feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())
.add(orderFeeForm.getParkingFee())
.add(orderFeeForm.getOtherFee())
.add(orderInfo.getFavourFee());
feeRuleResponseVo.setTotalAmount(totalAmount);

// 计算系统奖励
String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";
String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";
Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();

RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();
rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());
rewardRuleRequestForm.setOrderNum(orderNum);

RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();

// 计算分账信息
ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();
profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());
profitsharingRuleRequestForm.setOrderNum(orderNum);

ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();

// 封装实体类,结束代驾更新订单,添加账单和分账信息
UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();
updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());
updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());
//路桥费、停车费、其他费用
updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());
updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());
updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());
//乘客好处费
updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());

//实际里程
updateOrderBillForm.setRealDistance(realDistance);
//订单奖励信息
BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);
//代驾费用信息
BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);
//分账相关信息
BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);
updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());
orderInfoFeignClient.endDrive(updateOrderBillForm);

return true;
}

我的订单和异步编排

我的订单

  • 乘客端或司机端,都有我的订单,都可以查看用户所有的订单

乘客端我的订单

订单微服务接口

OrderInfoController

1
2
3
4
5
6
7
8
9
10
11
12
13
@Operation(summary = "获取乘客订单分页列表")  
@GetMapping("/findCustomerOrderPage/{customerId}/{page}/{limit}")
public Result<PageVo> findCustomerOrderPage(@PathVariable Long customerId,
@PathVariable Long page,
@PathVariable Long limit) {
//创建page对象
Page<OrderInfo> pageParam = new Page<>(page,limit);
//调用service方法实现分页条件查询
PageVo pageVo = orderInfoService.findCustomerOrderPage(pageParam,customerId);
pageVo.setPage(page);
pageVo.setLimit(limit);
return Result.ok(pageVo);
}

service

1
2
3
4
5
6
//获取乘客订单分页列表  
@Override
public PageVo findCustomerOrderPage(Page<OrderInfo> pageParam, Long customerId) {
IPage<OrderListVo> pageInfo = orderInfoMapper.selectCustomerOrderPage(pageParam,customerId);
return new PageVo<>(pageInfo.getRecords(),pageInfo.getPages(),pageInfo.getTotal());
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
8
9
10
11
/**  
* 获取乘客订单分页列表
* @param customerId
* @param page
* @param limit
* @return
*/
@GetMapping("/order/info/findCustomerOrderPage/{customerId}/{page}/{limit}")
Result<PageVo> findCustomerOrderPage(@PathVariable("customerId") Long customerId,
@PathVariable("page") Long page,
@PathVariable("limit") Long limit);

乘客web端调用

OrderController

1
2
3
4
5
6
7
8
9
10
11
12
13
@Operation(summary = "获取乘客订单分页列表")  
@LoginDetection
@GetMapping("findCustomerOrderPage/{page}/{limit}")
public Result<PageVo> findCustomerOrderPage(
@Parameter(name = "page", description = "当前页码", required = true)
@PathVariable Long page,

@Parameter(name = "limit", description = "每页记录数", required = true)
@PathVariable Long limit) {
Long customerId = AuthContextHolder.getUserId();
PageVo pageVo = orderService.findCustomerOrderPage(customerId, page, limit);
return Result.ok(pageVo);
}

service

1
2
3
4
5
// 获取乘客订单分页列表  
@Override
public PageVo findCustomerOrderPage(Long customerId, Long page, Long limit) {
return orderInfoFeignClient.findCustomerOrderPage(customerId,page,limit).getData();
}

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--查询乘客订单分页-->
<select id="selectCustomerOrderPage" resultType="com.atguigu.daijia.model.vo.order.OrderListVo">
select
info.id,
info.order_no,
info.start_location,
info.end_location,

if(info.status &lt; 7, info.expect_amount, bill.pay_amount) as amount,

info.status,
info.create_time
from order_info info left join order_bill bill on info.id = bill.order_id
where info.customer_id = #{customerId}
and info.is_deleted =0
order by info.create_time desc
</select>

司机端我的订单

订单微服务接口

OrderInfoController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Operation(summary = "获取司机订单分页列表")  
@GetMapping("/findDriverOrderPage/{driverId}/{page}/{limit}")
public Result<PageVo> findDriverOrderPage(
@Parameter(name = "driverId", description = "司机id", required = true)
@PathVariable Long driverId,
@Parameter(name = "page", description = "当前页码", required = true)
@PathVariable Long page,
@Parameter(name = "limit", description = "每页记录数", required = true)
@PathVariable Long limit) {
Page<OrderInfo> pageParam = new Page<>(page, limit);
PageVo pageVo = orderInfoService.findDriverOrderPage(pageParam, driverId);
pageVo.setPage(page);
pageVo.setLimit(limit);
return Result.ok(pageVo);
}

service

1
2
3
4
5
6
// 获取司机订单分页列表  
@Override
public PageVo findDriverOrderPage(Page<OrderInfo> pageParam, Long driverId) {
IPage<OrderListVo> pageInfo = orderInfoMapper.selectDriverOrderPage(pageParam,driverId);
return new PageVo<>(pageInfo.getRecords(),pageInfo.getPages(),pageInfo.getTotal());
}

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="selectDriverOrderPage" resultType="com.atguigu.daijia.model.vo.order.OrderListVo">
select
info.id,
info.order_no,
info.start_location,
info.end_location,
real_amount as pay_amount,
if(info.status &lt; 7, info.expect_amount, info.real_amount) as amount,

info.status,
info.create_time
from order_info info
where info.driver_id = #{driverId}
and info.is_deleted =0
order by info.create_time desc
</select>

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
8
9
10
11
/**  
* 获取司机订单分页列表
* @param driverId
* @param page
* @param limit
* @return
*/
@GetMapping("/order/info/findDriverOrderPage/{driverId}/{page}/{limit}")
Result<PageVo> findDriverOrderPage(@PathVariable("driverId") Long driverId,
@PathVariable("page") Long page,
@PathVariable("limit") Long limit);

司机端web接口

OrderController

1
2
3
4
5
6
7
8
9
10
11
12
@Operation(summary = "获取司机订单分页列表")  
@LoginDetection
@GetMapping("findDriverOrderPage/{page}/{limit}")
public Result<PageVo> findDriverOrderPage(
@Parameter(name = "page", description = "当前页码", required = true)
@PathVariable Long page,
@Parameter(name = "limit", description = "每页记录数", required = true)
@PathVariable Long limit) {
Long driverId = AuthContextHolder.getUserId();
PageVo pageVo = orderService.findDriverOrderPage(driverId, page, limit);
return Result.ok(pageVo);
}

service

1
2
3
4
5
// 获取司机订单分页列表  
@Override
public PageVo findDriverOrderPage(Long driverId, Long page, Long limit) {
return orderInfoFeignClient.findDriverOrderPage(driverId,page,limit).getData();
}

异步编排

  • 问题:司机结束订单后会有大量的远程调用,如果按照流程来会浪费大量时间
  • 解决:使用多线程方式来完成这些操作

创建自定义线程池

在config包下创建类ThreadPoolConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration  
public class ThreadPoolConfig {

@Bean
public ThreadPoolExecutor threadPoolExecutor() {

//动态获取服务器核数
int processors = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
processors+1, // 核心线程个数 io:2n ,cpu: n+1 n:内核数据
processors+1,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
return threadPoolExecutor;
}
}

修改web-driver之前的代码

修改前面driver-web里OrderServiceImpl的endDrive方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//使用多线程CompletableFuture实现  
@SneakyThrows
public Boolean endDrive(OrderFeeForm orderFeeForm) {

//1 根据orderId获取订单信息,判断当前订单是否司机接单
CompletableFuture<OrderInfo> orderInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderFeeForm.getOrderId()).getData();
if (orderInfo.getDriverId() != orderFeeForm.getDriverId()) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}
return orderInfo;
});

//防止刷单
CompletableFuture<OrderServiceLastLocationVo> orderServiceLastLocationVoCompletableFuture = CompletableFuture.supplyAsync(() -> {
OrderServiceLastLocationVo orderServiceLastLocationVo = locationFeignClient.getOrderServiceLastLocation(orderFeeForm.getOrderId()).getData();
return orderServiceLastLocationVo;
});

//上面两个合并
CompletableFuture.allOf(orderInfoCompletableFuture,
orderServiceLastLocationVoCompletableFuture).join();

//获取两个线程执行结果
OrderInfo orderInfo = orderInfoCompletableFuture.get();

OrderServiceLastLocationVo orderServiceLastLocationVo = orderServiceLastLocationVoCompletableFuture.get();

//司机当前位置 距离 结束代驾位置
double distance = LocationUtil.getDistance(orderInfo.getEndPointLatitude().doubleValue(),
orderInfo.getEndPointLongitude().doubleValue(),
orderServiceLastLocationVo.getLatitude().doubleValue(),
orderServiceLastLocationVo.getLongitude().doubleValue());
if(distance > SystemConstant.DRIVER_END_LOCATION_DISTION) {
throw new GuiguException(ResultCodeEnum.DRIVER_END_LOCATION_DISTION_ERROR);
}

//2 计算订单实际里程
CompletableFuture<BigDecimal> realDistanceCompletableFuture = CompletableFuture.supplyAsync(() -> {
BigDecimal realDistance =
locationFeignClient.calculateOrderRealDistance(orderFeeForm.getOrderId()).getData();
return realDistance;
});

//3 计算代驾实际费用
CompletableFuture<FeeRuleResponseVo> feeRuleResponseVoCompletableFuture =
realDistanceCompletableFuture.thenApplyAsync((realDistance) -> {
//远程调用,计算代驾费用
//封装FeeRuleRequestForm
FeeRuleRequestForm feeRuleRequestForm = new FeeRuleRequestForm();
feeRuleRequestForm.setDistance(realDistance);
feeRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());

//计算司机到达代驾开始位置时间
//orderInfo.getArriveTime() - orderInfo.getAcceptTime()
// 分钟 = 毫秒 / 1000 * 60 Integer waitMinute =
Math.abs((int) ((orderInfo.getArriveTime().getTime() - orderInfo.getAcceptTime().getTime()) / (1000 * 60)));
feeRuleRequestForm.setWaitMinute(waitMinute);
//远程调用 代驾费用
FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(feeRuleRequestForm).getData();
//实际费用 = 代驾费用 + 其他费用(停车费)
BigDecimal totalAmount =
feeRuleResponseVo.getTotalAmount().add(orderFeeForm.getTollFee())
.add(orderFeeForm.getParkingFee())
.add(orderFeeForm.getOtherFee())
.add(orderInfo.getFavourFee());
feeRuleResponseVo.setTotalAmount(totalAmount);
return feeRuleResponseVo;
});

//4 计算系统奖励
CompletableFuture<Long> orderNumCompletableFuture = CompletableFuture.supplyAsync(() -> {
String startTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 00:00:00";
String endTime = new DateTime(orderInfo.getStartServiceTime()).toString("yyyy-MM-dd") + " 24:00:00";
Long orderNum = orderInfoFeignClient.getOrderNumByTime(startTime, endTime).getData();
return orderNum;
});

CompletableFuture<RewardRuleResponseVo> rewardRuleResponseVoCompletableFuture =
orderNumCompletableFuture.thenApplyAsync((orderNum) -> {
//4.2.封装参数
RewardRuleRequestForm rewardRuleRequestForm = new RewardRuleRequestForm();
rewardRuleRequestForm.setStartTime(orderInfo.getStartServiceTime());
rewardRuleRequestForm.setOrderNum(orderNum);
RewardRuleResponseVo rewardRuleResponseVo = rewardRuleFeignClient.calculateOrderRewardFee(rewardRuleRequestForm).getData();

return rewardRuleResponseVo;
});


//5 计算分账信息
CompletableFuture<ProfitsharingRuleResponseVo> profitsharingRuleResponseVoCompletableFuture = feeRuleResponseVoCompletableFuture.thenCombineAsync(orderNumCompletableFuture,
(feeRuleResponseVo, orderNum) -> {
ProfitsharingRuleRequestForm profitsharingRuleRequestForm = new ProfitsharingRuleRequestForm();
profitsharingRuleRequestForm.setOrderAmount(feeRuleResponseVo.getTotalAmount());
profitsharingRuleRequestForm.setOrderNum(orderNum);

ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleFeignClient.calculateOrderProfitsharingFee(profitsharingRuleRequestForm).getData();
return profitsharingRuleResponseVo;
});

//合并
CompletableFuture.allOf(
orderInfoCompletableFuture,
realDistanceCompletableFuture,
feeRuleResponseVoCompletableFuture,
orderNumCompletableFuture,
rewardRuleResponseVoCompletableFuture,
profitsharingRuleResponseVoCompletableFuture
).join();

//获取执行结果
BigDecimal realDistance = realDistanceCompletableFuture.get();
FeeRuleResponseVo feeRuleResponseVo = feeRuleResponseVoCompletableFuture.get();
RewardRuleResponseVo rewardRuleResponseVo = rewardRuleResponseVoCompletableFuture.get();
ProfitsharingRuleResponseVo profitsharingRuleResponseVo = profitsharingRuleResponseVoCompletableFuture.get();

//6 封装实体类,结束代驾更新订单,添加账单和分账信息
UpdateOrderBillForm updateOrderBillForm = new UpdateOrderBillForm();
updateOrderBillForm.setOrderId(orderFeeForm.getOrderId());
updateOrderBillForm.setDriverId(orderFeeForm.getDriverId());
//路桥费、停车费、其他费用
updateOrderBillForm.setTollFee(orderFeeForm.getTollFee());
updateOrderBillForm.setParkingFee(orderFeeForm.getParkingFee());
updateOrderBillForm.setOtherFee(orderFeeForm.getOtherFee());
//乘客好处费
updateOrderBillForm.setFavourFee(orderInfo.getFavourFee());

//实际里程
updateOrderBillForm.setRealDistance(realDistance);
//订单奖励信息
BeanUtils.copyProperties(rewardRuleResponseVo, updateOrderBillForm);
//代驾费用信息
BeanUtils.copyProperties(feeRuleResponseVo, updateOrderBillForm);
//分账相关信息
BeanUtils.copyProperties(profitsharingRuleResponseVo, updateOrderBillForm);
updateOrderBillForm.setProfitsharingRuleId(profitsharingRuleResponseVo.getProfitsharingRuleId());
orderInfoFeignClient.endDrive(updateOrderBillForm);

return true;
}

订单支付

账单信息

  • 司机结束代价后,生成账单(包含账单信息和分账信息)

获取账单信息

订单微服务接口

OrderInfoController

1
2
3
4
5
@Operation(summary = "根据订单id获取实际账单信息")  
@GetMapping("/getOrderBillInfo/{orderId}")
public Result<OrderBillVo> getOrderBillInfo(@PathVariable Long orderId) {
return Result.ok(orderInfoService.getOrderBillInfo(orderId));
}

service

1
2
3
4
5
6
7
8
9
10
11
// 根据订单id获取实际账单信息  
@Override
public OrderBillVo getOrderBillInfo(Long orderId) {
LambdaQueryWrapper<OrderBill> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderBill::getOrderId,orderId);
OrderBill orderBill = orderBillMapper.selectOne(wrapper);

OrderBillVo orderBillVo = new OrderBillVo();
BeanUtils.copyProperties(orderBill,orderBillVo);
return orderBillVo;
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**  
* 根据订单id获取实际账单信息
* @param orderId
* @return
*/
@GetMapping("/order/info/getOrderBillInfo/{orderId}")
Result<OrderBillVo> getOrderBillInfo(@PathVariable("orderId") Long orderId);

获取分账信息

订单微服务接口

OrderInfoController

1
2
3
4
5
@Operation(summary = "根据订单id获取实际分账信息")  
@GetMapping("/getOrderProfitsharing/{orderId}")
public Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable Long orderId) {
return Result.ok(orderInfoService.getOrderProfitsharing(orderId));
}

service

1
2
3
4
5
6
7
8
9
10
11
// 根据订单id获取实际分账信息  
@Override
public OrderProfitsharingVo getOrderProfitsharing(Long orderId) {
LambdaQueryWrapper<OrderProfitsharing> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderProfitsharing::getOrderId,orderId);
OrderProfitsharing orderProfitsharing = orderProfitsharingMapper.selectOne(wrapper);

OrderProfitsharingVo orderProfitsharingVo = new OrderProfitsharingVo();
BeanUtils.copyProperties(orderProfitsharing,orderProfitsharingVo);
return orderProfitsharingVo;
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
/**
* 根据订单id获取实际分账信息
* @param orderId
* @return
*/
@GetMapping("/order/info/getOrderProfitsharing/{orderId}")
Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable("orderId") Long orderId);

司机端获取账单信息

方法之前有,只需要改service
OrderController`

1
2
3
4
5
6
7
@Operation(summary = "获取订单账单详细信息")  
@LoginDetection
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.getOrderInfo(orderId, driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 获取订单账单详细信息  
@Override
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
if(orderInfo.getDriverId() != driverId) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

//获取账单和分账数据,封装到vo里面
OrderBillVo orderBillVo = null;
OrderProfitsharingVo orderProfitsharingVo = null;
//判断
if(orderInfo.getStatus() >= OrderStatus.END_SERVICE.getStatus()) {
//账单信息
orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();

//分账信息
orderProfitsharingVo = orderInfoFeignClient.getOrderProfitsharing(orderId).getData();
}

OrderInfoVo orderInfoVo = new OrderInfoVo();
orderInfoVo.setOrderId(orderId);
BeanUtils.copyProperties(orderInfo,orderInfoVo);
orderInfoVo.setOrderBillVo(orderBillVo);
orderInfoVo.setOrderProfitsharingVo(orderProfitsharingVo);
return orderInfoVo;
}

司机发送账单

订单微服务接口

OrderInfoController

1
2
3
4
5
@Operation(summary = "发送账单信息")  
@GetMapping("/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId, @PathVariable Long driverId) {
return Result.ok(orderInfoService.sendOrderBillInfo(orderId, driverId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 发送账单信息  
@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {
//更新订单信息
LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderInfo::getId, orderId);
queryWrapper.eq(OrderInfo::getDriverId, driverId);
//更新字段
OrderInfo updateOrderInfo = new OrderInfo();
updateOrderInfo.setStatus(OrderStatus.UNPAID.getStatus());
//只能更新自己的订单
int row = orderInfoMapper.update(updateOrderInfo, queryWrapper);
if(row == 1) {
return true;
} else {
throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
}
}

远程调用

OrderInfoFeignClient

1
2
3
4
5
6
7
8
/**  
* 司机发送账单信息
* @param orderId
* @param driverId
* @return
*/
@GetMapping("/order/info/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);

司机web端调用

OrderController

1
2
3
4
5
6
7
@Operation(summary = "司机发送账单信息")  
@LoginDetection
@GetMapping("/sendOrderBillInfo/{orderId}")
public Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.sendOrderBillInfo(orderId, driverId));
}

service

1
2
3
4
5
// 司机发送账单信息  
@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {
return orderInfoFeignClient.sendOrderBillInfo(orderId, driverId).getData();
}

乘客获取账单

也是修改之前写过的代码,只需要修改service
OrderController

1
2
3
4
5
6
7
@Operation(summary = "获取订单信息")  
@LoginDetection
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {
Long customerId = AuthContextHolder.getUserId();
return Result.ok(orderService.getOrderInfo(orderId, customerId));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 获取订单信息  
@Override
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {
OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
//判断
if(orderInfo.getCustomerId() != customerId) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

//获取司机信息
DriverInfoVo driverInfoVo = null;
Long driverId = orderInfo.getDriverId();
if(driverId != null) {
driverInfoVo = driverInfoFeignClient.getDriverInfo(driverId).getData();
}

//获取账单信息
OrderBillVo orderBillVo = null;
if(orderInfo.getStatus() >= OrderStatus.UNPAID.getStatus()) {
orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();
}

OrderInfoVo orderInfoVo = new OrderInfoVo();
orderInfoVo.setOrderId(orderId);
BeanUtils.copyProperties(orderInfo,orderInfoVo);
orderInfoVo.setOrderBillVo(orderBillVo);
orderInfoVo.setDriverInfoVo(driverInfoVo);
return orderInfoVo;
}

微信支付

准备接口

获取乘客openid

service-customer中的CustomerInfoController

1
2
3
4
5
@Operation(summary = "获取客户OpenId")  
@GetMapping("/getCustomerOpenId/{customerId}")
public Result<String> getCustomerOpenId(@PathVariable Long customerId) {
return Result.ok(customerInfoService.getCustomerOpenId(customerId));
}

service

1
2
3
4
5
6
7
8
// 获取客户OpenId  
@Override
public String getCustomerOpenId(Long customerId) {
LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustomerInfo::getId,customerId);
CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);
return customerInfo.getWxOpenId();
}

远程调用

CustomerInfoFeignClient

1
2
3
4
5
6
7
/**  
* 获取客户OpenId
* @param customerId
* @return
*/
@GetMapping("/customer/info/getCustomerOpenId/{customerId}")
Result<String> getCustomerOpenId(@PathVariable("customerId") Long customerId);

获取司机openid

service-driver中DriverInfoController

1
2
3
4
5
@Operation(summary = "获取司机OpenId")  
@GetMapping("/getDriverOpenId/{driverId}")
public Result<String> getDriverOpenId(@PathVariable Long driverId) {
return Result.ok(driverInfoService.getDriverOpenId(driverId));
}

serivce

1
2
3
4
5
6
// 获取司机OpenId  
@Override
public String getDriverOpenId(Long driverId) {
DriverInfo driverInfo = this.getOne(new LambdaQueryWrapper<DriverInfo>().eq(DriverInfo::getId, driverId).select(DriverInfo::getWxOpenId));
return driverInfo.getWxOpenId();
}

远程调用

DriverInfoFeignClient

1
2
3
4
5
6
7
/**  
* 获取司机OpenId
* @param driverId
* @return
*/
@GetMapping("/driver/info/getDriverOpenId/{driverId}")
Result<String> getDriverOpenId(@PathVariable("driverId") Long driverId);

获取支付信息

service-order中OrderInfoController

1
2
3
4
5
@Operation(summary = "获取订单支付信息")
@GetMapping("/getOrderPayVo/{orderNo}/{customerId}")
public Result<OrderPayVo> getOrderPayVo(@PathVariable String orderNo, @PathVariable Long customerId) {
return Result.ok(orderInfoService.getOrderPayVo(orderNo, customerId));
}

service

1
2
3
4
5
6
7
8
9
10
// 获取订单支付信息  
@Override
public OrderPayVo getOrderPayVo(String orderNo, Long customerId) {
OrderPayVo orderPayVo = orderInfoMapper.selectOrderPayVo(orderNo,customerId);
if(orderPayVo != null) {
String content = orderPayVo.getStartLocation() + " 到 "+orderPayVo.getEndLocation();
orderPayVo.setContent(content);
}
return orderPayVo;
}

远程调用

1
2
3
4
5
6
7
8
/**  
* 获取订单支付信息
* @param orderNo
* @param customerId
* @return
*/
@GetMapping("/order/info/getOrderPayVo/{orderNo}/{customerId}")
Result<OrderPayVo> getOrderPayVo(@PathVariable("orderNo") String orderNo, @PathVariable("customerId") Long customerId);

微信支付接口

在service-payment下

  • 导入依赖

    1
    2
    3
    4
    <dependency>  
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    </dependency>
  • 创建配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Configuration  
    @ConfigurationProperties(prefix="wx.v3pay") //读取节点
    @Data
    public class WxPayV3Properties {

    private String appid;
    /** 商户号 */
    public String merchantId;
    /** 商户API私钥路径 */
    public String privateKeyPath;
    /** 商户证书序列号 */
    public String merchantSerialNumber;
    /** 商户APIV3密钥 */
    public String apiV3key;
    /** 回调地址 */
    private String notifyUrl;

    @Bean
    public RSAAutoCertificateConfig getConfig(){
    return new RSAAutoCertificateConfig.Builder()
    .merchantId(this.getMerchantId())
    .privateKeyFromPath(this.getPrivateKeyPath())
    .merchantSerialNumber(this.getMerchantSerialNumber())
    .apiV3Key(this.getApiV3key())
    .build();

    }
    }

WxPayController

1
2
3
4
5
6
7
8
@Autowired  
private WxPayService wxPayService;

@Operation(summary = "创建微信支付")
@PostMapping("/createJsapi")
public Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm) {
return Result.ok(wxPayService.createWxPayment(paymentInfoForm));
}
  • 远程调用

WxPayFeignClient

1
2
3
4
5
6
7
/**  
* 创建微信支付
* @param paymentInfoForm
* @return
*/
@PostMapping("/payment/wxPay/createWxPayment")
Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm);
  • 乘客web端调用

OrderController

1
2
3
4
5
6
7
8
@Operation(summary = "创建微信支付")  
@LoginDetection
@PostMapping("/createWxPayment")
public Result<WxPrepayVo> createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {
Long customerId = AuthContextHolder.getUserId();
createWxPaymentForm.setCustomerId(customerId);
return Result.ok(orderService.createWxPayment(createWxPaymentForm));
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override  
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {
//获取订单支付信息
OrderPayVo orderPayVo = orderInfoFeignClient.getOrderPayVo(createWxPaymentForm.getOrderNo(),
createWxPaymentForm.getCustomerId()).getData();
//判断
if(orderPayVo.getStatus() != OrderStatus.UNPAID.getStatus()) {
throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
}

//获取乘客和司机openid
String customerOpenId = customerInfoFeignClient.getCustomerOpenId(orderPayVo.getCustomerId()).getData();

String driverOpenId = driverInfoFeignClient.getDriverOpenId(orderPayVo.getDriverId()).getData();

//封装需要数据到实体类,远程调用发起微信支付
PaymentInfoForm paymentInfoForm = new PaymentInfoForm();
paymentInfoForm.setCustomerOpenId(customerOpenId);
paymentInfoForm.setDriverOpenId(driverOpenId);
paymentInfoForm.setOrderNo(orderPayVo.getOrderNo());
paymentInfoForm.setAmount(orderPayVo.getPayAmount());
paymentInfoForm.setContent(orderPayVo.getContent());
paymentInfoForm.setPayWay(1);

WxPrepayVo wxPrepayVo = wxPayFeignClient.createWxPayment(paymentInfoForm).getData();
return wxPrepayVo;
}

支付结果查询

评论